Skip to main content

Places controller for Guests, including localization

Commit Message

Places controller for Guests, including localization

If the i18n text is coming through in English when it should
be Spanish, make sure you've added `es` to the locales map
exported by api/src/conf/locales/index.ts and that you have
updated `supportedLocales`, exported by api/src/utils/i18n.ts,
to `LocalesEnumValues` (which ties back to the database level
enum of our supported locales).

If you get ambiguous Typescript errors along the way, restart
the ESLint server in your editor, and if that doesn't work,
quit and re-open your editor

```console
pnpm psy g:controller V1/Guest/Places index show

pnpm psy sync

pnpm build
pnpm build:spec

pnpm uspec spec/unit/controllers/V1/Guest/PlacesController.spec.ts
// If you get 400, use NODE_DEBUG=psychic to see OpenAPI validation errors
NODE_DEBUG=psychic pnpm uspec spec/unit/controllers/V1/Guest/PlacesController.spec.ts
pnpm uspec
```

Changes

diff --git a/api/spec/factories/LocalizedTextFactory.ts b/api/spec/factories/LocalizedTextFactory.ts
index 9a2923b..66beb3d 100644
--- a/api/spec/factories/LocalizedTextFactory.ts
+++ b/api/spec/factories/LocalizedTextFactory.ts
@@ -1,5 +1,6 @@
import LocalizedText from '@models/LocalizedText.js'
import { UpdateableProperties } from '@rvoh/dream/types'
+import createPlace from '@spec/factories/PlaceFactory.js'

let counter = 0

diff --git a/api/spec/unit/controllers/V1/Guest/PlacesController.spec.ts b/api/spec/unit/controllers/V1/Guest/PlacesController.spec.ts
new file mode 100644
index 0000000..c35a19a
--- /dev/null
+++ b/api/spec/unit/controllers/V1/Guest/PlacesController.spec.ts
@@ -0,0 +1,152 @@
+import Place from '@models/Place.js'
+import User from '@models/User.js'
+import createLocalizedText from '@spec/factories/LocalizedTextFactory.js'
+import createPlace from '@spec/factories/PlaceFactory.js'
+import createBathroom from '@spec/factories/Room/BathroomFactory.js'
+import createBedroom from '@spec/factories/Room/BedroomFactory.js'
+import createDen from '@spec/factories/Room/DenFactory.js'
+import createKitchen from '@spec/factories/Room/KitchenFactory.js'
+import createLivingRoom from '@spec/factories/Room/LivingRoomFactory.js'
+import createUser from '@spec/factories/UserFactory.js'
+import { SpecRequestType, session } from '@spec/unit/helpers/authentication.js'
+
+describe('V1/Guest/PlacesController', () => {
+ let request: SpecRequestType
+ let user: User
+
+ beforeEach(async () => {
+ user = await createUser()
+ request = await session(user)
+ })
+
+ describe('GET index', () => {
+ const subject = async <StatusCode extends 200 | 400>(expectedStatus: StatusCode) => {
+ return request.get('/v1/guest/places', expectedStatus, {
+ headers: {
+ 'content-language': 'es-ES',
+ },
+ })
+ }
+
+ it('returns the index of Places', async () => {
+ const place = await createPlace()
+ await createLocalizedText({ localizable: place, locale: 'es-ES', title: 'The Spanish title' })
+
+ const { body } = await subject(200)
+
+ expect(body.results).toEqual([
+ {
+ id: place.id,
+ title: 'The Spanish title',
+ },
+ ])
+ })
+ })
+
+ describe('GET show', () => {
+ const subject = async <StatusCode extends 200 | 400>(place: Place, expectedStatus: StatusCode) => {
+ return request.get('/v1/guest/places/{id}', expectedStatus, {
+ id: place.id,
+ headers: {
+ 'content-language': 'es-ES',
+ },
+ })
+ }
+
+ it.only('returns places with rooms', async () => {
+ const place = await createPlace({ style: 'cabin', sleeps: 3 })
+ await createLocalizedText({ localizable: place, locale: 'es-ES', title: 'The Spanish place title' })
+
+ const { kitchen, bathroom, bedroom, den, livingRoom } = await createRoomsForPlace(place)
+
+ const { body } = await subject(place, 200)
+
+ expect(body).toEqual({
+ id: place.id,
+ sleeps: 3,
+ title: 'The Spanish place title',
+ style: 'cabin',
+ displayStyle: 'cabaña rústica',
+
+ rooms: [
+ {
+ id: kitchen.id,
+ type: 'Kitchen',
+ displayType: 'cocina',
+ title: 'The Spanish kitchen title',
+ appliances: [
+ {
+ value: 'oven',
+ label: 'horno',
+ },
+ {
+ value: 'dishwasher',
+ label: 'lavavajillas',
+ },
+ ],
+ },
+ {
+ id: bathroom.id,
+ type: 'Bathroom',
+ displayType: 'baño',
+ title: 'The Spanish bathroom title',
+ bathOrShowerStyle: {
+ value: 'shower',
+ label: 'ducha',
+ },
+ },
+ {
+ id: bedroom.id,
+ type: 'Bedroom',
+ displayType: 'dormitorio',
+ title: 'The Spanish bedroom title',
+ bedTypes: [
+ {
+ value: 'cot',
+ label: 'catre',
+ },
+ {
+ value: 'bunk',
+ label: 'litera',
+ },
+ ],
+ },
+ { id: den.id, type: 'Den', displayType: 'estudio', title: 'The Spanish den title' },
+ {
+ id: livingRoom.id,
+ type: 'LivingRoom',
+ displayType: 'sala de estar',
+ title: 'The Spanish livingRoom title',
+ },
+ ],
+ })
+ })
+ })
+})
+
+async function createRoomsForPlace(place: Place) {
+ const kitchen = await createKitchen({ place, appliances: ['oven', 'dishwasher'] })
+ await createLocalizedText({ localizable: kitchen, locale: 'es-ES', title: 'The Spanish kitchen title' })
+
+ const bathroom = await createBathroom({ place, bathOrShowerStyle: 'shower' })
+ await createLocalizedText({
+ localizable: bathroom,
+ locale: 'es-ES',
+ title: 'The Spanish bathroom title',
+ })
+
+ const bedroom = await createBedroom({ place, bedTypes: ['cot', 'bunk'] })
+ await createLocalizedText({ localizable: bedroom, locale: 'es-ES', title: 'The Spanish bedroom title' })
+
+ const den = await createDen({ place })
+ await createLocalizedText({ localizable: den, locale: 'es-ES', title: 'The Spanish den title' })
+
+ const livingRoom = await createLivingRoom({ place })
+ await createLocalizedText({
+ localizable: livingRoom,
+ locale: 'es-ES',
+ title: 'The Spanish livingRoom title',
+ })
+
+ return { kitchen, bathroom, bedroom, den, livingRoom }
+}
diff --git a/api/src/app/controllers/AuthedController.ts b/api/src/app/controllers/AuthedController.ts
index 7f37ec2..159c124 100644
--- a/api/src/app/controllers/AuthedController.ts
+++ b/api/src/app/controllers/AuthedController.ts
@@ -3,6 +3,7 @@ import ApplicationController from '@controllers/ApplicationController.js'
import User from '@models/User.js'
import { Encrypt } from '@rvoh/dream/utils'
import { BeforeAction } from '@rvoh/psychic'
+import { supportedLocales } from '@src/utils/i18n.js'

export default class AuthedController extends ApplicationController {
protected currentUser: User
@@ -18,6 +19,19 @@ export default class AuthedController extends ApplicationController {
this.currentUser = user
}

+ @BeforeAction()
+ public configureSerializers() {
+ this.serializerPassthrough({
+ locale: this.locale,
+ })
+ }
+
+ protected get locale() {
+ const locale = this.headers['content-language']
+ const locales = supportedLocales()
+ return locales.includes(locale as (typeof locales)[number]) ? locale : 'en-US'
+ }
+
protected authedUserId(): string | null {
if (!AppEnv.isTest)
throw new Error(
diff --git a/api/src/app/controllers/V1/Guest/BaseController.ts b/api/src/app/controllers/V1/Guest/BaseController.ts
new file mode 100644
index 0000000..652ec53
--- /dev/null
+++ b/api/src/app/controllers/V1/Guest/BaseController.ts
@@ -0,0 +1,5 @@
+import V1BaseController from '../BaseController.js'
+
+export default class V1GuestBaseController extends V1BaseController {
+
+}
diff --git a/api/src/app/controllers/V1/Guest/PlacesController.ts b/api/src/app/controllers/V1/Guest/PlacesController.ts
new file mode 100644
index 0000000..4460021
--- /dev/null
+++ b/api/src/app/controllers/V1/Guest/PlacesController.ts
@@ -0,0 +1,37 @@
+import Place from '@models/Place.js'
+import { OpenAPI } from '@rvoh/psychic'
+import V1GuestBaseController from './BaseController.js'
+
+const openApiTags = ['guest-places']
+
+export default class V1GuestPlacesController extends V1GuestBaseController {
+ @OpenAPI(Place, {
+ status: 200,
+ tags: openApiTags,
+ description: 'Place index endpoint for Guests',
+ cursorPaginate: true,
+ serializerKey: 'summaryForGuests',
+ fastJsonStringify: true,
+ })
+ public async index() {
+ const places = await Place.passthrough({ locale: this.locale })
+ .preloadFor('summaryForGuests')
+ .cursorPaginate({ cursor: this.castParam('cursor', 'string', { allowNull: true }) })
+ this.ok(places)
+ }
+
+ @OpenAPI(Place, {
+ status: 200,
+ tags: openApiTags,
+ description: 'Place show endpoint for Guests',
+ serializerKey: 'forGuests',
+ fastJsonStringify: true,
+ })
+ public async show() {
+ this.ok(
+ await Place.passthrough({ locale: this.locale })
+ .preloadFor('forGuests')
+ .findOrFail(this.castParam('id', 'uuid')),
+ )
+ }
+}
diff --git a/api/src/app/models/Place.ts b/api/src/app/models/Place.ts
index 9e6d48f..a7f0a8a 100644
--- a/api/src/app/models/Place.ts
+++ b/api/src/app/models/Place.ts
@@ -17,6 +17,8 @@ export default class Place extends ApplicationModel {
return {
default: 'PlaceSerializer',
summary: 'PlaceSummarySerializer',
+ summaryForGuests: 'PlaceSummaryForGuestsSerializer',
+ forGuests: 'PlaceForGuestsSerializer',
}
}

diff --git a/api/src/app/models/Room/Bathroom.ts b/api/src/app/models/Room/Bathroom.ts
index 881ead9..e196696 100644
--- a/api/src/app/models/Room/Bathroom.ts
+++ b/api/src/app/models/Room/Bathroom.ts
@@ -10,6 +10,7 @@ export default class Bathroom extends Room {
return {
default: 'Room/BathroomSerializer',
summary: 'Room/BathroomSummarySerializer',
+ forGuests: 'Room/BathroomForGuestsSerializer',
}
}

diff --git a/api/src/app/models/Room/Bedroom.ts b/api/src/app/models/Room/Bedroom.ts
index 2fb83d7..69d4d29 100644
--- a/api/src/app/models/Room/Bedroom.ts
+++ b/api/src/app/models/Room/Bedroom.ts
@@ -10,6 +10,7 @@ export default class Bedroom extends Room {
return {
default: 'Room/BedroomSerializer',
summary: 'Room/BedroomSummarySerializer',
+ forGuests: 'Room/BedroomForGuestsSerializer',
}
}

diff --git a/api/src/app/models/Room/Den.ts b/api/src/app/models/Room/Den.ts
index 0f80449..9e3ba44 100644
--- a/api/src/app/models/Room/Den.ts
+++ b/api/src/app/models/Room/Den.ts
@@ -10,6 +10,7 @@ export default class Den extends Room {
return {
default: 'Room/DenSerializer',
summary: 'Room/DenSummarySerializer',
+ forGuests: 'Room/DenForGuestsSerializer',
}
}
}
diff --git a/api/src/app/models/Room/Kitchen.ts b/api/src/app/models/Room/Kitchen.ts
index 84eebb2..88e86e7 100644
--- a/api/src/app/models/Room/Kitchen.ts
+++ b/api/src/app/models/Room/Kitchen.ts
@@ -10,6 +10,7 @@ export default class Kitchen extends Room {
return {
default: 'Room/KitchenSerializer',
summary: 'Room/KitchenSummarySerializer',
+ forGuests: 'Room/KitchenForGuestsSerializer',
}
}

diff --git a/api/src/app/models/Room/LivingRoom.ts b/api/src/app/models/Room/LivingRoom.ts
index 4231e84..bc9eadf 100644
--- a/api/src/app/models/Room/LivingRoom.ts
+++ b/api/src/app/models/Room/LivingRoom.ts
@@ -10,6 +10,7 @@ export default class LivingRoom extends Room {
return {
default: 'Room/LivingRoomSerializer',
summary: 'Room/LivingRoomSummarySerializer',
+ forGuests: 'Room/LivingRoomForGuestsSerializer',
}
}
}
diff --git a/api/src/app/serializers/PlaceSerializer.ts b/api/src/app/serializers/PlaceSerializer.ts
index ff993f0..4ae5e31 100644
--- a/api/src/app/serializers/PlaceSerializer.ts
+++ b/api/src/app/serializers/PlaceSerializer.ts
@@ -1,5 +1,7 @@
import Place from '@models/Place.js'
import { DreamSerializer } from '@rvoh/dream'
+import { LocalesEnum } from '@src/types/db.js'
+import i18n from '@src/utils/i18n.js'

export const PlaceSummarySerializer = (place: Place) =>
DreamSerializer(Place, place)
@@ -11,3 +13,17 @@ export const PlaceSerializer = (place: Place) =>
.attribute('style')
.attribute('sleeps')
.attribute('deletedAt')
+
+export const PlaceSummaryForGuestsSerializer = (place: Place) =>
+ DreamSerializer(Place, place)
+ .attribute('id')
+ .delegatedAttribute('currentLocalizedText', 'title', { openapi: 'string' })
+
+export const PlaceForGuestsSerializer = (place: Place, passthrough: { locale: LocalesEnum }) =>
+ PlaceSummaryForGuestsSerializer(place)
+ .attribute('style')
+ .customAttribute('displayStyle', () => i18n(passthrough.locale, `places.style.${place.style}`), {
+ openapi: 'string',
+ })
+ .attribute('sleeps')
+ .rendersMany('rooms', { serializerKey: 'forGuests' })
diff --git a/api/src/app/serializers/Room/BathroomSerializer.ts b/api/src/app/serializers/Room/BathroomSerializer.ts
index a80452b..a0f7560 100644
--- a/api/src/app/serializers/Room/BathroomSerializer.ts
+++ b/api/src/app/serializers/Room/BathroomSerializer.ts
@@ -1,5 +1,8 @@
-import { RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
import Bathroom from '@models/Room/Bathroom.js'
+import { ObjectSerializer } from '@rvoh/dream'
+import { RoomForGuestsSerializer, RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
+import { BathOrShowerStylesEnum, BathOrShowerStylesEnumValues, LocalesEnum } from '@src/types/db.js'
+import i18n from '@src/utils/i18n.js'

export const RoomBathroomSummarySerializer = (bathroom: Bathroom) =>
RoomSummarySerializer(Bathroom, bathroom)
@@ -7,3 +10,27 @@ export const RoomBathroomSummarySerializer = (bathroom: Bathroom) =>
export const RoomBathroomSerializer = (bathroom: Bathroom) =>
RoomSerializer(Bathroom, bathroom)
.attribute('bathOrShowerStyle')
+
+export const BathOrShowerStyleSerializer = (
+ bathOrShowerStyle: BathOrShowerStylesEnum,
+ passthrough: { locale: LocalesEnum },
+) =>
+ ObjectSerializer({ bathOrShowerStyle }, passthrough)
+ .attribute('bathOrShowerStyle', {
+ as: 'value',
+ openapi: { type: 'string', enum: BathOrShowerStylesEnumValues },
+ })
+ .customAttribute(
+ 'label',
+ () => i18n(passthrough.locale, `rooms.Bathroom.bathOrShowerStyles.${bathOrShowerStyle}`),
+ {
+ openapi: 'string',
+ },
+ )
+
+export const RoomBathroomForGuestsSerializer = (
+ roomBathroom: Bathroom,
+ passthrough: { locale: LocalesEnum },
+) =>
+ RoomForGuestsSerializer(Bathroom, roomBathroom, passthrough)
+ .rendersOne('bathOrShowerStyle', { serializer: BathOrShowerStyleSerializer })
diff --git a/api/src/app/serializers/Room/BedroomSerializer.ts b/api/src/app/serializers/Room/BedroomSerializer.ts
index f34b5b4..f206507 100644
--- a/api/src/app/serializers/Room/BedroomSerializer.ts
+++ b/api/src/app/serializers/Room/BedroomSerializer.ts
@@ -1,5 +1,8 @@
-import { RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
import Bedroom from '@models/Room/Bedroom.js'
+import { ObjectSerializer } from '@rvoh/dream'
+import { RoomForGuestsSerializer, RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
+import { BedTypesEnum, BedTypesEnumValues, LocalesEnum } from '@src/types/db.js'
+import i18n from '@src/utils/i18n.js'

export const RoomBedroomSummarySerializer = (bedroom: Bedroom) =>
RoomSummarySerializer(Bedroom, bedroom)
@@ -7,3 +10,15 @@ export const RoomBedroomSummarySerializer = (bedroom: Bedroom) =>
export const RoomBedroomSerializer = (bedroom: Bedroom) =>
RoomSerializer(Bedroom, bedroom)
.attribute('bedTypes')
+
+export const BedTypeSerializer = (bedType: BedTypesEnum, passthrough: { locale: LocalesEnum }) =>
+ ObjectSerializer({ bedType }, passthrough)
+ .attribute('bedType', { as: 'value', openapi: { type: 'string', enum: BedTypesEnumValues } })
+ .customAttribute('label', () => i18n(passthrough.locale, `rooms.Bedroom.bedTypes.${bedType}`), {
+ openapi: 'string',
+ })
+
+export const RoomBedroomForGuestsSerializer = (roomBedroom: Bedroom, passthrough: { locale: LocalesEnum }) =>
+ RoomForGuestsSerializer(Bedroom, roomBedroom, passthrough).rendersMany('bedTypes', {
+ serializer: BedTypeSerializer,
+ })
diff --git a/api/src/app/serializers/Room/DenSerializer.ts b/api/src/app/serializers/Room/DenSerializer.ts
index 5d4db09..a62b888 100644
--- a/api/src/app/serializers/Room/DenSerializer.ts
+++ b/api/src/app/serializers/Room/DenSerializer.ts
@@ -1,8 +1,12 @@
-import { RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
import Den from '@models/Room/Den.js'
+import { RoomForGuestsSerializer, RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
+import { LocalesEnum } from '@src/types/db.js'

export const RoomDenSummarySerializer = (den: Den) =>
RoomSummarySerializer(Den, den)

export const RoomDenSerializer = (den: Den) =>
RoomSerializer(Den, den)
+
+export const RoomDenForGuestsSerializer = (roomDen: Den, passthrough: { locale: LocalesEnum }) =>
+ RoomForGuestsSerializer(Den, roomDen, passthrough)
diff --git a/api/src/app/serializers/Room/KitchenSerializer.ts b/api/src/app/serializers/Room/KitchenSerializer.ts
index 633aa8f..7f42f47 100644
--- a/api/src/app/serializers/Room/KitchenSerializer.ts
+++ b/api/src/app/serializers/Room/KitchenSerializer.ts
@@ -1,5 +1,8 @@
-import { RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
import Kitchen from '@models/Room/Kitchen.js'
+import { ObjectSerializer } from '@rvoh/dream'
+import { RoomForGuestsSerializer, RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
+import { ApplianceTypesEnum, ApplianceTypesEnumValues, LocalesEnum } from '@src/types/db.js'
+import i18n from '@src/utils/i18n.js'

export const RoomKitchenSummarySerializer = (kitchen: Kitchen) =>
RoomSummarySerializer(Kitchen, kitchen)
@@ -7,3 +10,15 @@ export const RoomKitchenSummarySerializer = (kitchen: Kitchen) =>
export const RoomKitchenSerializer = (kitchen: Kitchen) =>
RoomSerializer(Kitchen, kitchen)
.attribute('appliances')
+
+export const ApplianceSerializer = (appliance: ApplianceTypesEnum, passthrough: { locale: LocalesEnum }) =>
+ ObjectSerializer({ appliance }, passthrough)
+ .attribute('appliance', { as: 'value', openapi: { type: 'string', enum: ApplianceTypesEnumValues } })
+ .customAttribute('label', () => i18n(passthrough.locale, `rooms.Kitchen.appliances.${appliance}`), {
+ openapi: 'string',
+ })
+
+export const RoomKitchenForGuestsSerializer = (roomKitchen: Kitchen, passthrough: { locale: LocalesEnum }) =>
+ RoomForGuestsSerializer(Kitchen, roomKitchen, passthrough).rendersMany('appliances', {
+ serializer: ApplianceSerializer,
+ })
diff --git a/api/src/app/serializers/Room/LivingRoomSerializer.ts b/api/src/app/serializers/Room/LivingRoomSerializer.ts
index 473d39b..a5b91f4 100644
--- a/api/src/app/serializers/Room/LivingRoomSerializer.ts
+++ b/api/src/app/serializers/Room/LivingRoomSerializer.ts
@@ -1,8 +1,14 @@
-import { RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
import LivingRoom from '@models/Room/LivingRoom.js'
+import { RoomForGuestsSerializer, RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
+import { LocalesEnum } from '@src/types/db.js'

export const RoomLivingRoomSummarySerializer = (livingRoom: LivingRoom) =>
RoomSummarySerializer(LivingRoom, livingRoom)

export const RoomLivingRoomSerializer = (livingRoom: LivingRoom) =>
RoomSerializer(LivingRoom, livingRoom)
+
+export const RoomLivingRoomForGuestsSerializer = (
+ roomLivingRoom: LivingRoom,
+ passthrough: { locale: LocalesEnum },
+) => RoomForGuestsSerializer(LivingRoom, roomLivingRoom, passthrough)
diff --git a/api/src/app/serializers/RoomSerializer.ts b/api/src/app/serializers/RoomSerializer.ts
index 7a26a36..ba2237c 100644
--- a/api/src/app/serializers/RoomSerializer.ts
+++ b/api/src/app/serializers/RoomSerializer.ts
@@ -1,5 +1,7 @@
import Room from '@models/Room.js'
import { DreamSerializer } from '@rvoh/dream'
+import { LocalesEnum } from '@src/types/db.js'
+import i18n from '@src/utils/i18n.js'

export const RoomSummarySerializer = <T extends Room>(StiChildClass: typeof Room, room: T) =>
DreamSerializer(StiChildClass ?? Room, room)
@@ -10,3 +12,16 @@ export const RoomSummarySerializer = <T extends Room>(StiChildClass: typeof Room
export const RoomSerializer = <T extends Room>(StiChildClass: typeof Room, room: T) =>
RoomSummarySerializer(StiChildClass, room)
.attribute('deletedAt')
+
+export const RoomForGuestsSerializer = <T extends Room>(
+ StiChildClass: typeof Room,
+ room: T,
+ passthrough: { locale: LocalesEnum },
+) =>
+ DreamSerializer(StiChildClass ?? Room, room)
+ .attribute('id')
+ .attribute('type')
+ .customAttribute('displayType', () => i18n(passthrough.locale, `rooms.type.${room.type}`), {
+ openapi: 'string',
+ })
+ .delegatedAttribute<Room, 'currentLocalizedText'>('currentLocalizedText', 'title', { openapi: 'string' })
diff --git a/api/src/conf/locales/en.ts b/api/src/conf/locales/en.ts
index ae21584..1e720ed 100644
--- a/api/src/conf/locales/en.ts
+++ b/api/src/conf/locales/en.ts
@@ -1,3 +1,52 @@
export default {
- // add your application's localizable text in here
+ places: {
+ style: {
+ cottage: 'cottage',
+ cabin: 'cabin',
+ lean_to: 'lean to',
+ treehouse: 'treehouse',
+ tent: 'tent',
+ cave: 'cave',
+ dump: 'dump',
+ },
+ },
+
+ rooms: {
+ type: {
+ Bathroom: 'bathroom',
+ Bedroom: 'bedroom',
+ Kitchen: 'kitchen',
+ Den: 'den',
+ LivingRoom: 'living room',
+ },
+
+ Bathroom: {
+ bathOrShowerStyles: {
+ bath: 'bath',
+ shower: 'shower',
+ bath_and_shower: 'bath and shower',
+ none: 'none',
+ },
+ },
+
+ Bedroom: {
+ bedTypes: {
+ twin: 'twin',
+ bunk: 'bunk',
+ queen: 'queen',
+ king: 'king',
+ cot: 'cot',
+ sofabed: 'sofabed',
+ },
+ },
+
+ Kitchen: {
+ appliances: {
+ stove: 'stove',
+ oven: 'oven',
+ microwave: 'microwave',
+ dishwasher: 'dishwasher',
+ },
+ },
+ },
}
diff --git a/api/src/conf/locales/es.ts b/api/src/conf/locales/es.ts
new file mode 100644
index 0000000..25eb876
--- /dev/null
+++ b/api/src/conf/locales/es.ts
@@ -0,0 +1,52 @@
+export default {
+ places: {
+ style: {
+ cottage: 'cabaña',
+ cabin: 'cabaña rústica',
+ lean_to: 'refugio',
+ treehouse: 'casa del árbol',
+ tent: 'tienda de campaña',
+ cave: 'cueva',
+ dump: 'basurero',
+ },
+ },
+
+ rooms: {
+ type: {
+ Bathroom: 'baño',
+ Bedroom: 'dormitorio',
+ Kitchen: 'cocina',
+ Den: 'estudio',
+ LivingRoom: 'sala de estar',
+ },
+
+ Bathroom: {
+ bathOrShowerStyles: {
+ bath: 'bañera',
+ shower: 'ducha',
+ bath_and_shower: 'bañera y ducha',
+ none: 'ninguno',
+ },
+ },
+
+ Bedroom: {
+ bedTypes: {
+ twin: 'individual',
+ bunk: 'litera',
+ queen: 'matrimonial',
+ king: 'king',
+ cot: 'catre',
+ sofabed: 'sofá cama',
+ },
+ },
+
+ Kitchen: {
+ appliances: {
+ stove: 'estufa',
+ oven: 'horno',
+ microwave: 'microondas',
+ dishwasher: 'lavavajillas',
+ },
+ },
+ },
+}
diff --git a/api/src/conf/locales/index.ts b/api/src/conf/locales/index.ts
index b798cf2..82b076e 100644
--- a/api/src/conf/locales/index.ts
+++ b/api/src/conf/locales/index.ts
@@ -1,5 +1,7 @@
import en from '@conf/locales/en.js'
+import es from '@conf/locales/es.js'

export default {
en,
+ es,
}
diff --git a/api/src/conf/routes.ts b/api/src/conf/routes.ts
index 054e588..e41a5dc 100644
--- a/api/src/conf/routes.ts
+++ b/api/src/conf/routes.ts
@@ -4,14 +4,19 @@ import { PsychicRouter } from '@rvoh/psychic'

export default function routes(r: PsychicRouter) {
r.namespace('v1', r => {
+ r.namespace('guest', r => {
+ r.resources('places', { only: ['index', 'show'] })
+ // Alternatively, could have written the routes explicitly:
+ // r.get('places', V1GuestPlacesController, 'index')
+ // r.get('places/:id', V1GuestPlacesController, 'show')
+ })
+
r.namespace('host', r => {
r.resources('localized-texts', { only: ['update', 'destroy'] })

r.resources('places', r => {
r.resources('rooms')
-
})
-
})
})

diff --git a/api/src/openapi/mobile.openapi.json b/api/src/openapi/mobile.openapi.json
index f4e9077..00455ae 100644
--- a/api/src/openapi/mobile.openapi.json
+++ b/api/src/openapi/mobile.openapi.json
@@ -6,6 +6,131 @@
"description": "The autogenerated openapi spec for your app"
},
"paths": {
+ "/v1/guest/places": {
+ "parameters": [
+ {
+ "in": "query",
+ "required": false,
+ "name": "cursor",
+ "description": "Pagination cursor",
+ "allowReserved": true,
+ "schema": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ ],
+ "get": {
+ "tags": [
+ "guest-places"
+ ],
+ "description": "Place index endpoint for Guests",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "cursor",
+ "results"
+ ],
+ "properties": {
+ "cursor": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PlaceSummaryForGuests"
+ }
+ }
+ }
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "$ref": "#/components/responses/BadRequest"
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "409": {
+ "$ref": "#/components/responses/Conflict"
+ },
+ "422": {
+ "$ref": "#/components/responses/ValidationErrors"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ }
+ }
+ },
+ "/v1/guest/places/{id}": {
+ "parameters": [
+ {
+ "in": "path",
+ "name": "id",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "get": {
+ "tags": [
+ "guest-places"
+ ],
+ "description": "Place show endpoint for Guests",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PlaceForGuests"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "$ref": "#/components/responses/BadRequest"
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "409": {
+ "$ref": "#/components/responses/Conflict"
+ },
+ "422": {
+ "$ref": "#/components/responses/ValidationErrors"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ }
+ }
+ },
"/v1/host/localized-texts/{id}": {
"parameters": [
{
@@ -821,6 +946,57 @@
},
"components": {
"schemas": {
+ "Appliance": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "description": "\nThe following values will be allowed:\n dishwasher,\n microwave,\n oven,\n stove"
+ }
+ }
+ },
+ "BathOrShowerStyle": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "description": "\nThe following values will be allowed:\n bath,\n bath_and_shower,\n none,\n shower"
+ }
+ }
+ },
+ "BedType": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "description": "\nThe following values will be allowed:\n bunk,\n cot,\n king,\n queen,\n sofabed,\n twin"
+ }
+ }
+ },
"OpenapiValidationErrors": {
"type": "object",
"required": [
@@ -909,6 +1085,58 @@
}
}
},
+ "PlaceForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "displayStyle",
+ "id",
+ "rooms",
+ "sleeps",
+ "style",
+ "title"
+ ],
+ "properties": {
+ "displayStyle": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "rooms": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/RoomBathroomForGuests"
+ },
+ {
+ "$ref": "#/components/schemas/RoomBedroomForGuests"
+ },
+ {
+ "$ref": "#/components/schemas/RoomDenForGuests"
+ },
+ {
+ "$ref": "#/components/schemas/RoomKitchenForGuests"
+ },
+ {
+ "$ref": "#/components/schemas/RoomLivingRoomForGuests"
+ }
+ ]
+ }
+ },
+ "sleeps": {
+ "type": "integer"
+ },
+ "style": {
+ "type": "string",
+ "description": "The following values will be allowed:\n cabin,\n cave,\n cottage,\n dump,\n lean_to,\n tent,\n treehouse"
+ },
+ "title": {
+ "type": "string"
+ }
+ }
+ },
"PlaceSummary": {
"type": "object",
"additionalProperties": false,
@@ -925,6 +1153,22 @@
}
}
},
+ "PlaceSummaryForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "title"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ }
+ }
+ },
"RoomBathroom": {
"type": "object",
"additionalProperties": false,
@@ -965,6 +1209,35 @@
}
}
},
+ "RoomBathroomForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "bathOrShowerStyle",
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "bathOrShowerStyle": {
+ "$ref": "#/components/schemas/BathOrShowerStyle"
+ },
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "description": "The following values will be allowed:\n Bathroom,\n Bedroom,\n Den,\n Kitchen,\n LivingRoom"
+ }
+ }
+ },
"RoomBathroomSummary": {
"type": "object",
"additionalProperties": false,
@@ -1029,6 +1302,38 @@
}
}
},
+ "RoomBedroomForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "bedTypes",
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "bedTypes": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/BedType"
+ }
+ },
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "description": "The following values will be allowed:\n Bathroom,\n Bedroom,\n Den,\n Kitchen,\n LivingRoom"
+ }
+ }
+ },
"RoomBedroomSummary": {
"type": "object",
"additionalProperties": false,
@@ -1085,6 +1390,31 @@
}
}
},
+ "RoomDenForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "description": "The following values will be allowed:\n Bathroom,\n Bedroom,\n Den,\n Kitchen,\n LivingRoom"
+ }
+ }
+ },
"RoomDenSummary": {
"type": "object",
"additionalProperties": false,
@@ -1149,6 +1479,38 @@
}
}
},
+ "RoomKitchenForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "appliances",
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "appliances": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Appliance"
+ }
+ },
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "description": "The following values will be allowed:\n Bathroom,\n Bedroom,\n Den,\n Kitchen,\n LivingRoom"
+ }
+ }
+ },
"RoomKitchenSummary": {
"type": "object",
"additionalProperties": false,
@@ -1205,6 +1567,31 @@
}
}
},
+ "RoomLivingRoomForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "description": "The following values will be allowed:\n Bathroom,\n Bedroom,\n Den,\n Kitchen,\n LivingRoom"
+ }
+ }
+ },
"RoomLivingRoomSummary": {
"type": "object",
"additionalProperties": false,
diff --git a/api/src/openapi/openapi.json b/api/src/openapi/openapi.json
index 10bf720..ec3e47b 100644
--- a/api/src/openapi/openapi.json
+++ b/api/src/openapi/openapi.json
@@ -6,6 +6,131 @@
"description": "The autogenerated openapi spec for your app"
},
"paths": {
+ "/v1/guest/places": {
+ "parameters": [
+ {
+ "in": "query",
+ "required": false,
+ "name": "cursor",
+ "description": "Pagination cursor",
+ "allowReserved": true,
+ "schema": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ ],
+ "get": {
+ "tags": [
+ "guest-places"
+ ],
+ "description": "Place index endpoint for Guests",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "cursor",
+ "results"
+ ],
+ "properties": {
+ "cursor": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PlaceSummaryForGuests"
+ }
+ }
+ }
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "$ref": "#/components/responses/BadRequest"
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "409": {
+ "$ref": "#/components/responses/Conflict"
+ },
+ "422": {
+ "$ref": "#/components/responses/ValidationErrors"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ }
+ }
+ },
+ "/v1/guest/places/{id}": {
+ "parameters": [
+ {
+ "in": "path",
+ "name": "id",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "get": {
+ "tags": [
+ "guest-places"
+ ],
+ "description": "Place show endpoint for Guests",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PlaceForGuests"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "$ref": "#/components/responses/BadRequest"
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "409": {
+ "$ref": "#/components/responses/Conflict"
+ },
+ "422": {
+ "$ref": "#/components/responses/ValidationErrors"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ }
+ }
+ },
"/v1/host/localized-texts/{id}": {
"parameters": [
{
@@ -821,6 +946,74 @@
},
"components": {
"schemas": {
+ "Appliance": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "dishwasher",
+ "microwave",
+ "oven",
+ "stove"
+ ]
+ }
+ }
+ },
+ "BathOrShowerStyle": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "bath",
+ "bath_and_shower",
+ "none",
+ "shower"
+ ]
+ }
+ }
+ },
+ "BedType": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "bunk",
+ "cot",
+ "king",
+ "queen",
+ "sofabed",
+ "twin"
+ ]
+ }
+ }
+ },
"OpenapiValidationErrors": {
"type": "object",
"required": [
@@ -917,6 +1110,66 @@
}
}
},
+ "PlaceForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "displayStyle",
+ "id",
+ "rooms",
+ "sleeps",
+ "style",
+ "title"
+ ],
+ "properties": {
+ "displayStyle": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "rooms": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/RoomBathroomForGuests"
+ },
+ {
+ "$ref": "#/components/schemas/RoomBedroomForGuests"
+ },
+ {
+ "$ref": "#/components/schemas/RoomDenForGuests"
+ },
+ {
+ "$ref": "#/components/schemas/RoomKitchenForGuests"
+ },
+ {
+ "$ref": "#/components/schemas/RoomLivingRoomForGuests"
+ }
+ ]
+ }
+ },
+ "sleeps": {
+ "type": "integer"
+ },
+ "style": {
+ "type": "string",
+ "enum": [
+ "cabin",
+ "cave",
+ "cottage",
+ "dump",
+ "lean_to",
+ "tent",
+ "treehouse"
+ ]
+ },
+ "title": {
+ "type": "string"
+ }
+ }
+ },
"PlaceSummary": {
"type": "object",
"additionalProperties": false,
@@ -933,6 +1186,22 @@
}
}
},
+ "PlaceSummaryForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "title"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ }
+ }
+ },
"RoomBathroom": {
"type": "object",
"additionalProperties": false,
@@ -981,6 +1250,41 @@
}
}
},
+ "RoomBathroomForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "bathOrShowerStyle",
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "bathOrShowerStyle": {
+ "$ref": "#/components/schemas/BathOrShowerStyle"
+ },
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "Bathroom",
+ "Bedroom",
+ "Den",
+ "Kitchen",
+ "LivingRoom"
+ ]
+ }
+ }
+ },
"RoomBathroomSummary": {
"type": "object",
"additionalProperties": false,
@@ -1056,6 +1360,44 @@
}
}
},
+ "RoomBedroomForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "bedTypes",
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "bedTypes": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/BedType"
+ }
+ },
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "Bathroom",
+ "Bedroom",
+ "Den",
+ "Kitchen",
+ "LivingRoom"
+ ]
+ }
+ }
+ },
"RoomBedroomSummary": {
"type": "object",
"additionalProperties": false,
@@ -1116,6 +1458,37 @@
}
}
},
+ "RoomDenForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "Bathroom",
+ "Bedroom",
+ "Den",
+ "Kitchen",
+ "LivingRoom"
+ ]
+ }
+ }
+ },
"RoomDenSummary": {
"type": "object",
"additionalProperties": false,
@@ -1189,6 +1562,44 @@
}
}
},
+ "RoomKitchenForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "appliances",
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "appliances": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Appliance"
+ }
+ },
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "Bathroom",
+ "Bedroom",
+ "Den",
+ "Kitchen",
+ "LivingRoom"
+ ]
+ }
+ }
+ },
"RoomKitchenSummary": {
"type": "object",
"additionalProperties": false,
@@ -1249,6 +1660,37 @@
}
}
},
+ "RoomLivingRoomForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "Bathroom",
+ "Bedroom",
+ "Den",
+ "Kitchen",
+ "LivingRoom"
+ ]
+ }
+ }
+ },
"RoomLivingRoomSummary": {
"type": "object",
"additionalProperties": false,
diff --git a/api/src/openapi/tests.openapi.json b/api/src/openapi/tests.openapi.json
index 8d6ad26..fe36df4 100644
--- a/api/src/openapi/tests.openapi.json
+++ b/api/src/openapi/tests.openapi.json
@@ -6,6 +6,131 @@
"description": "The autogenerated openapi spec for your app"
},
"paths": {
+ "/v1/guest/places": {
+ "parameters": [
+ {
+ "in": "query",
+ "required": false,
+ "name": "cursor",
+ "description": "Pagination cursor",
+ "allowReserved": true,
+ "schema": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ }
+ ],
+ "get": {
+ "tags": [
+ "guest-places"
+ ],
+ "description": "Place index endpoint for Guests",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "cursor",
+ "results"
+ ],
+ "properties": {
+ "cursor": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PlaceSummaryForGuests"
+ }
+ }
+ }
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "$ref": "#/components/responses/BadRequest"
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "409": {
+ "$ref": "#/components/responses/Conflict"
+ },
+ "422": {
+ "$ref": "#/components/responses/ValidationErrors"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ }
+ }
+ },
+ "/v1/guest/places/{id}": {
+ "parameters": [
+ {
+ "in": "path",
+ "name": "id",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "get": {
+ "tags": [
+ "guest-places"
+ ],
+ "description": "Place show endpoint for Guests",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PlaceForGuests"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "$ref": "#/components/responses/BadRequest"
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "403": {
+ "$ref": "#/components/responses/Forbidden"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "409": {
+ "$ref": "#/components/responses/Conflict"
+ },
+ "422": {
+ "$ref": "#/components/responses/ValidationErrors"
+ },
+ "500": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ }
+ }
+ },
"/v1/host/localized-texts/{id}": {
"parameters": [
{
@@ -821,6 +946,74 @@
},
"components": {
"schemas": {
+ "Appliance": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "dishwasher",
+ "microwave",
+ "oven",
+ "stove"
+ ]
+ }
+ }
+ },
+ "BathOrShowerStyle": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "bath",
+ "bath_and_shower",
+ "none",
+ "shower"
+ ]
+ }
+ }
+ },
+ "BedType": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "bunk",
+ "cot",
+ "king",
+ "queen",
+ "sofabed",
+ "twin"
+ ]
+ }
+ }
+ },
"OpenapiValidationErrors": {
"type": "object",
"required": [
@@ -917,6 +1110,66 @@
}
}
},
+ "PlaceForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "displayStyle",
+ "id",
+ "rooms",
+ "sleeps",
+ "style",
+ "title"
+ ],
+ "properties": {
+ "displayStyle": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "rooms": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/RoomBathroomForGuests"
+ },
+ {
+ "$ref": "#/components/schemas/RoomBedroomForGuests"
+ },
+ {
+ "$ref": "#/components/schemas/RoomDenForGuests"
+ },
+ {
+ "$ref": "#/components/schemas/RoomKitchenForGuests"
+ },
+ {
+ "$ref": "#/components/schemas/RoomLivingRoomForGuests"
+ }
+ ]
+ }
+ },
+ "sleeps": {
+ "type": "integer"
+ },
+ "style": {
+ "type": "string",
+ "enum": [
+ "cabin",
+ "cave",
+ "cottage",
+ "dump",
+ "lean_to",
+ "tent",
+ "treehouse"
+ ]
+ },
+ "title": {
+ "type": "string"
+ }
+ }
+ },
"PlaceSummary": {
"type": "object",
"additionalProperties": false,
@@ -933,6 +1186,22 @@
}
}
},
+ "PlaceSummaryForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "title"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ }
+ }
+ },
"RoomBathroom": {
"type": "object",
"additionalProperties": false,
@@ -981,6 +1250,41 @@
}
}
},
+ "RoomBathroomForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "bathOrShowerStyle",
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "bathOrShowerStyle": {
+ "$ref": "#/components/schemas/BathOrShowerStyle"
+ },
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "Bathroom",
+ "Bedroom",
+ "Den",
+ "Kitchen",
+ "LivingRoom"
+ ]
+ }
+ }
+ },
"RoomBathroomSummary": {
"type": "object",
"additionalProperties": false,
@@ -1056,6 +1360,44 @@
}
}
},
+ "RoomBedroomForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "bedTypes",
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "bedTypes": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/BedType"
+ }
+ },
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "Bathroom",
+ "Bedroom",
+ "Den",
+ "Kitchen",
+ "LivingRoom"
+ ]
+ }
+ }
+ },
"RoomBedroomSummary": {
"type": "object",
"additionalProperties": false,
@@ -1116,6 +1458,37 @@
}
}
},
+ "RoomDenForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "Bathroom",
+ "Bedroom",
+ "Den",
+ "Kitchen",
+ "LivingRoom"
+ ]
+ }
+ }
+ },
"RoomDenSummary": {
"type": "object",
"additionalProperties": false,
@@ -1189,6 +1562,44 @@
}
}
},
+ "RoomKitchenForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "appliances",
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "appliances": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Appliance"
+ }
+ },
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "Bathroom",
+ "Bedroom",
+ "Den",
+ "Kitchen",
+ "LivingRoom"
+ ]
+ }
+ }
+ },
"RoomKitchenSummary": {
"type": "object",
"additionalProperties": false,
@@ -1249,6 +1660,37 @@
}
}
},
+ "RoomLivingRoomForGuests": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "Bathroom",
+ "Bedroom",
+ "Den",
+ "Kitchen",
+ "LivingRoom"
+ ]
+ }
+ }
+ },
"RoomLivingRoomSummary": {
"type": "object",
"additionalProperties": false,
diff --git a/api/src/types/dream.globals.ts b/api/src/types/dream.globals.ts
index 0f826c1..a540ffc 100644
--- a/api/src/types/dream.globals.ts
+++ b/api/src/types/dream.globals.ts
@@ -64,18 +64,29 @@ export const globalTypeConfig = {
'HostSummarySerializer',
'LocalizedTextSerializer',
'LocalizedTextSummarySerializer',
+ 'PlaceForGuestsSerializer',
'PlaceSerializer',
+ 'PlaceSummaryForGuestsSerializer',
'PlaceSummarySerializer',
+ 'Room/ApplianceSerializer',
+ 'Room/BathOrShowerStyleSerializer',
+ 'Room/BathroomForGuestsSerializer',
'Room/BathroomSerializer',
'Room/BathroomSummarySerializer',
+ 'Room/BedTypeSerializer',
+ 'Room/BedroomForGuestsSerializer',
'Room/BedroomSerializer',
'Room/BedroomSummarySerializer',
+ 'Room/DenForGuestsSerializer',
'Room/DenSerializer',
'Room/DenSummarySerializer',
+ 'Room/KitchenForGuestsSerializer',
'Room/KitchenSerializer',
'Room/KitchenSummarySerializer',
+ 'Room/LivingRoomForGuestsSerializer',
'Room/LivingRoomSerializer',
'Room/LivingRoomSummarySerializer',
+ 'RoomForGuestsSerializer',
'RoomSerializer',
'RoomSummarySerializer',
],
diff --git a/api/src/types/dream.ts b/api/src/types/dream.ts
index b3e680f..449b430 100644
--- a/api/src/types/dream.ts
+++ b/api/src/types/dream.ts
@@ -438,7 +438,7 @@ export const schema = {
},
},
places: {
- serializerKeys: ['default', 'summary'],
+ serializerKeys: ['default', 'forGuests', 'summary', 'summaryForGuests'],
scopes: {
default: [],
named: [],
@@ -567,7 +567,7 @@ export const schema = {
},
},
rooms: {
- serializerKeys: ['default', 'summary'],
+ serializerKeys: ['default', 'forGuests', 'summary'],
scopes: {
default: ['dream:STI'],
named: [],
diff --git a/api/src/types/openapi/tests.openapi.d.ts b/api/src/types/openapi/tests.openapi.d.ts
index b2bfc20..41b906e 100644
--- a/api/src/types/openapi/tests.openapi.d.ts
+++ b/api/src/types/openapi/tests.openapi.d.ts
@@ -1,4 +1,103 @@
export interface paths {
+ "/v1/guest/places": {
+ parameters: {
+ query?: {
+ /** @description Pagination cursor */
+ cursor?: string | null;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** @description Place index endpoint for Guests */
+ get: {
+ parameters: {
+ query?: {
+ /** @description Pagination cursor */
+ cursor?: string | null;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Success */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ cursor: string | null;
+ results: components["schemas"]["PlaceSummaryForGuests"][];
+ };
+ };
+ };
+ 400: components["responses"]["BadRequest"];
+ 401: components["responses"]["Unauthorized"];
+ 403: components["responses"]["Forbidden"];
+ 404: components["responses"]["NotFound"];
+ 409: components["responses"]["Conflict"];
+ 422: components["responses"]["ValidationErrors"];
+ 500: components["responses"]["InternalServerError"];
+ };
+ };
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/v1/guest/places/{id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ id: string;
+ };
+ cookie?: never;
+ };
+ /** @description Place show endpoint for Guests */
+ get: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Success */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["PlaceForGuests"];
+ };
+ };
+ 400: components["responses"]["BadRequest"];
+ 401: components["responses"]["Unauthorized"];
+ 403: components["responses"]["Forbidden"];
+ 404: components["responses"]["NotFound"];
+ 409: components["responses"]["Conflict"];
+ 422: components["responses"]["ValidationErrors"];
+ 500: components["responses"]["InternalServerError"];
+ };
+ };
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/v1/host/localized-texts/{id}": {
parameters: {
query?: never;
@@ -470,6 +569,21 @@ export interface paths {
export type webhooks = Record<string, never>;
export interface components {
schemas: {
+ Appliance: {
+ label: string;
+ /** @enum {string} */
+ value: "dishwasher" | "microwave" | "oven" | "stove";
+ };
+ BathOrShowerStyle: {
+ label: string;
+ /** @enum {string} */
+ value: "bath" | "bath_and_shower" | "none" | "shower";
+ };
+ BedType: {
+ label: string;
+ /** @enum {string} */
+ value: "bunk" | "cot" | "king" | "queen" | "sofabed" | "twin";
+ };
OpenapiValidationErrors: {
/** @enum {string} */
type: "openapi";
@@ -492,10 +606,23 @@ export interface components {
/** @enum {string} */
style: "cabin" | "cave" | "cottage" | "dump" | "lean_to" | "tent" | "treehouse";
};
+ PlaceForGuests: {
+ displayStyle: string;
+ id: string;
+ rooms: (components["schemas"]["RoomBathroomForGuests"] | components["schemas"]["RoomBedroomForGuests"] | components["schemas"]["RoomDenForGuests"] | components["schemas"]["RoomKitchenForGuests"] | components["schemas"]["RoomLivingRoomForGuests"])[];
+ sleeps: number;
+ /** @enum {string} */
+ style: "cabin" | "cave" | "cottage" | "dump" | "lean_to" | "tent" | "treehouse";
+ title: string;
+ };
PlaceSummary: {
id: string;
name: string;
};
+ PlaceSummaryForGuests: {
+ id: string;
+ title: string;
+ };
RoomBathroom: {
/** @enum {string|null} */
bathOrShowerStyle: "bath" | "bath_and_shower" | "none" | "shower" | null;
@@ -506,6 +633,14 @@ export interface components {
/** @enum {string} */
type: "Bathroom";
};
+ RoomBathroomForGuests: {
+ bathOrShowerStyle: components["schemas"]["BathOrShowerStyle"];
+ displayType: string;
+ id: string;
+ title: string;
+ /** @enum {string} */
+ type: "Bathroom" | "Bedroom" | "Den" | "Kitchen" | "LivingRoom";
+ };
RoomBathroomSummary: {
id: string;
position: number | null;
@@ -521,6 +656,14 @@ export interface components {
/** @enum {string} */
type: "Bedroom";
};
+ RoomBedroomForGuests: {
+ bedTypes: components["schemas"]["BedType"][];
+ displayType: string;
+ id: string;
+ title: string;
+ /** @enum {string} */
+ type: "Bathroom" | "Bedroom" | "Den" | "Kitchen" | "LivingRoom";
+ };
RoomBedroomSummary: {
id: string;
position: number | null;
@@ -535,6 +678,13 @@ export interface components {
/** @enum {string} */
type: "Den";
};
+ RoomDenForGuests: {
+ displayType: string;
+ id: string;
+ title: string;
+ /** @enum {string} */
+ type: "Bathroom" | "Bedroom" | "Den" | "Kitchen" | "LivingRoom";
+ };
RoomDenSummary: {
id: string;
position: number | null;
@@ -550,6 +700,14 @@ export interface components {
/** @enum {string} */
type: "Kitchen";
};
+ RoomKitchenForGuests: {
+ appliances: components["schemas"]["Appliance"][];
+ displayType: string;
+ id: string;
+ title: string;
+ /** @enum {string} */
+ type: "Bathroom" | "Bedroom" | "Den" | "Kitchen" | "LivingRoom";
+ };
RoomKitchenSummary: {
id: string;
position: number | null;
@@ -564,6 +722,13 @@ export interface components {
/** @enum {string} */
type: "LivingRoom";
};
+ RoomLivingRoomForGuests: {
+ displayType: string;
+ id: string;
+ title: string;
+ /** @enum {string} */
+ type: "Bathroom" | "Bedroom" | "Den" | "Kitchen" | "LivingRoom";
+ };
RoomLivingRoomSummary: {
id: string;
position: number | null;
diff --git a/api/src/utils/i18n.ts b/api/src/utils/i18n.ts
index c99a20e..b5ec778 100644
--- a/api/src/utils/i18n.ts
+++ b/api/src/utils/i18n.ts
@@ -1,9 +1,9 @@
import locales from '@conf/locales/index.js'
import { I18nProvider } from '@rvoh/psychic/system'
+import { LocalesEnumValues } from '@src/types/db.js'

-const SUPPORTED_LOCALES = ['en-US']
export function supportedLocales() {
- return SUPPORTED_LOCALES
+ return LocalesEnumValues
}

export default I18nProvider.provide(locales, 'en')