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 --help
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/unit/controllers/V1/Guest/PlacesController.spec.ts b/api/spec/unit/controllers/V1/Guest/PlacesController.spec.ts
new file mode 100644
index 0000000..8b17948
--- /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 { session, SpecRequestType } 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: {
+ 'accept-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: {
+ 'accept-language': 'es-ES',
+ },
+ })
+ }
+
+ it('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/ApplicationController.ts b/api/src/app/controllers/ApplicationController.ts
index db258f4..2daa964 100644
--- a/api/src/app/controllers/ApplicationController.ts
+++ b/api/src/app/controllers/ApplicationController.ts
@@ -1,6 +1,7 @@
-import { PsychicController } from '@rvoh/psychic'
+import { BeforeAction, PsychicController } from '@rvoh/psychic'
import { PsychicOpenapiNames } from '@rvoh/psychic/openapi'
import psychicTypes from '@src/types/psychic.js'
+import { supportedLocales } from '@src/utils/i18n.js'

export default class ApplicationController extends PsychicController {
public override get psychicTypes() {
@@ -10,4 +11,17 @@ export default class ApplicationController extends PsychicController {
public static override get openapiNames(): PsychicOpenapiNames<ApplicationController> {
return ['default', 'mobile', 'tests']
}
+
+ @BeforeAction()
+ public configureSerializers() {
+ this.serializerPassthrough({
+ locale: this.locale,
+ })
+ }
+
+ protected get locale() {
+ const locale = this.header('Accept-Language')
+ const locales = supportedLocales()
+ return locales.includes(locale as (typeof locales)[number]) ? locale : 'en-US'
+ }
}
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..0fc9479
--- /dev/null
+++ b/api/src/app/controllers/V1/Guest/BaseController.ts
@@ -0,0 +1,3 @@
+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..4cccbef
--- /dev/null
+++ b/api/src/app/controllers/V1/Guest/PlacesController.ts
@@ -0,0 +1,37 @@
+import { OpenAPI } from '@rvoh/psychic'
+import Place from '@models/Place.js'
+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 a1be52d..926eb44 100644
--- a/api/src/app/models/Place.ts
+++ b/api/src/app/models/Place.ts
@@ -18,6 +18,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 ae1bc18..ab46c42 100644
--- a/api/src/app/models/Room/Bathroom.ts
+++ b/api/src/app/models/Room/Bathroom.ts
@@ -12,6 +12,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 ed0eeb4..42cecda 100644
--- a/api/src/app/models/Room/Bedroom.ts
+++ b/api/src/app/models/Room/Bedroom.ts
@@ -12,6 +12,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 2c0a986..725b28d 100644
--- a/api/src/app/models/Room/Den.ts
+++ b/api/src/app/models/Room/Den.ts
@@ -12,6 +12,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 a38b947..34fe457 100644
--- a/api/src/app/models/Room/Kitchen.ts
+++ b/api/src/app/models/Room/Kitchen.ts
@@ -12,6 +12,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 1069ae8..ac7eeba 100644
--- a/api/src/app/models/Room/LivingRoom.ts
+++ b/api/src/app/models/Room/LivingRoom.ts
@@ -12,6 +12,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 bc76022..f49b98e 100644
--- a/api/src/app/serializers/PlaceSerializer.ts
+++ b/api/src/app/serializers/PlaceSerializer.ts
@@ -1,5 +1,7 @@
import { DreamSerializer } from '@rvoh/dream'
import Place from '@models/Place.js'
+import { type LocalesEnum } from '@src/types/db.js'
+import i18n from '@src/utils/i18n.js'

export const PlaceSummarySerializer = (place: Place) =>
DreamSerializer(Place, place)
@@ -10,3 +12,17 @@ export const PlaceSerializer = (place: Place) =>
PlaceSummarySerializer(place)
.attribute('style')
.attribute('sleeps')
+
+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..9ef8d33 100644
--- a/api/src/app/serializers/Room/BathroomSerializer.ts
+++ b/api/src/app/serializers/Room/BathroomSerializer.ts
@@ -1,5 +1,12 @@
-import { RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
+import { ObjectSerializer } from '@rvoh/dream'
+import { RoomForGuestsSerializer, RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
import Bathroom from '@models/Room/Bathroom.js'
+import {
+ type BathOrShowerStylesEnum,
+ BathOrShowerStylesEnumValues,
+ type LocalesEnum,
+} from '@src/types/db.js'
+import i18n from '@src/utils/i18n.js'

export const RoomBathroomSummarySerializer = (bathroom: Bathroom) =>
RoomSummarySerializer(Bathroom, bathroom)
@@ -7,3 +14,24 @@ 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<Bathroom>('bathOrShowerStyle', { serializer: BathOrShowerStyleSerializer })
diff --git a/api/src/app/serializers/Room/BedroomSerializer.ts b/api/src/app/serializers/Room/BedroomSerializer.ts
index f34b5b4..efce021 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 { ObjectSerializer } from '@rvoh/dream'
+import { RoomForGuestsSerializer, RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
import Bedroom from '@models/Room/Bedroom.js'
+import { type BedTypesEnum, BedTypesEnumValues, type 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<Bedroom>('bedTypes', {
+ serializer: BedTypeSerializer,
+ })
diff --git a/api/src/app/serializers/Room/DenSerializer.ts b/api/src/app/serializers/Room/DenSerializer.ts
index 5d4db09..2ebffd3 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 { RoomForGuestsSerializer, RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
import Den from '@models/Room/Den.js'
+import { type 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..6c109d3 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 { ObjectSerializer } from '@rvoh/dream'
+import { RoomForGuestsSerializer, RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
import Kitchen from '@models/Room/Kitchen.js'
+import { type ApplianceTypesEnum, ApplianceTypesEnumValues, type 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<Kitchen>('appliances', {
+ serializer: ApplianceSerializer,
+ })
diff --git a/api/src/app/serializers/Room/LivingRoomSerializer.ts b/api/src/app/serializers/Room/LivingRoomSerializer.ts
index 473d39b..ef92947 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 { RoomForGuestsSerializer, RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
import LivingRoom from '@models/Room/LivingRoom.js'
+import { type 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 d16e185..7ffd3c7 100644
--- a/api/src/app/serializers/RoomSerializer.ts
+++ b/api/src/app/serializers/RoomSerializer.ts
@@ -1,5 +1,7 @@
import { DreamSerializer } from '@rvoh/dream'
import Room from '@models/Room.js'
+import { type 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)
@@ -9,3 +11,16 @@ export const RoomSummarySerializer = <T extends Room>(StiChildClass: typeof Room

export const RoomSerializer = <T extends Room>(StiChildClass: typeof Room, room: T) =>
RoomSummarySerializer(StiChildClass, room)
+
+export const RoomForGuestsSerializer = <T extends Room>(
+ StiChildClass: typeof Room,
+ room: T,
+ passthrough: { locale: LocalesEnum }
+) =>
+ DreamSerializer(StiChildClass ?? Room, room)
+ .attribute('id')
+ .attribute('type', { openapi: { type: 'string', enum: [(StiChildClass ?? Room).sanitizedName] } })
+ .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 ff6749b..39ff67b 100644
--- a/api/src/conf/routes.ts
+++ b/api/src/conf/routes.ts
@@ -4,14 +4,16 @@ 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'] })
+ })
+
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 830fd35..0f8cc53 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,54 @@
},
"components": {
"schemas": {
+ "Appliance": {
+ "type": "object",
+ "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",
+ "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",
+ "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": [
@@ -900,6 +1073,57 @@
}
}
},
+ "PlaceForGuests": {
+ "type": "object",
+ "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",
"required": [
@@ -915,6 +1139,21 @@
}
}
},
+ "PlaceSummaryForGuests": {
+ "type": "object",
+ "required": [
+ "id",
+ "title"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ }
+ }
+ },
"RoomBathroom": {
"type": "object",
"required": [
@@ -946,6 +1185,34 @@
}
}
},
+ "RoomBathroomForGuests": {
+ "type": "object",
+ "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"
+ }
+ }
+ },
"RoomBathroomSummary": {
"type": "object",
"required": [
@@ -1000,6 +1267,37 @@
}
}
},
+ "RoomBedroomForGuests": {
+ "type": "object",
+ "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 Bedroom"
+ }
+ }
+ },
"RoomBedroomSummary": {
"type": "object",
"required": [
@@ -1046,6 +1344,30 @@
}
}
},
+ "RoomDenForGuests": {
+ "type": "object",
+ "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 Den"
+ }
+ }
+ },
"RoomDenSummary": {
"type": "object",
"required": [
@@ -1100,6 +1422,37 @@
}
}
},
+ "RoomKitchenForGuests": {
+ "type": "object",
+ "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 Kitchen"
+ }
+ }
+ },
"RoomKitchenSummary": {
"type": "object",
"required": [
@@ -1146,6 +1499,30 @@
}
}
},
+ "RoomLivingRoomForGuests": {
+ "type": "object",
+ "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 LivingRoom"
+ }
+ }
+ },
"RoomLivingRoomSummary": {
"type": "object",
"required": [
diff --git a/api/src/openapi/openapi.json b/api/src/openapi/openapi.json
index 37767da..a90973d 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,71 @@
},
"components": {
"schemas": {
+ "Appliance": {
+ "type": "object",
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "dishwasher",
+ "microwave",
+ "oven",
+ "stove"
+ ]
+ }
+ }
+ },
+ "BathOrShowerStyle": {
+ "type": "object",
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "bath",
+ "bath_and_shower",
+ "none",
+ "shower"
+ ]
+ }
+ }
+ },
+ "BedType": {
+ "type": "object",
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "bunk",
+ "cot",
+ "king",
+ "queen",
+ "sofabed",
+ "twin"
+ ]
+ }
+ }
+ },
"OpenapiValidationErrors": {
"type": "object",
"required": [
@@ -908,6 +1098,65 @@
}
}
},
+ "PlaceForGuests": {
+ "type": "object",
+ "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",
"required": [
@@ -923,6 +1172,21 @@
}
}
},
+ "PlaceSummaryForGuests": {
+ "type": "object",
+ "required": [
+ "id",
+ "title"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ }
+ }
+ },
"RoomBathroom": {
"type": "object",
"required": [
@@ -962,6 +1226,36 @@
}
}
},
+ "RoomBathroomForGuests": {
+ "type": "object",
+ "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"
+ ]
+ }
+ }
+ },
"RoomBathroomSummary": {
"type": "object",
"required": [
@@ -1027,6 +1321,39 @@
}
}
},
+ "RoomBedroomForGuests": {
+ "type": "object",
+ "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": [
+ "Bedroom"
+ ]
+ }
+ }
+ },
"RoomBedroomSummary": {
"type": "object",
"required": [
@@ -1077,6 +1404,32 @@
}
}
},
+ "RoomDenForGuests": {
+ "type": "object",
+ "required": [
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "Den"
+ ]
+ }
+ }
+ },
"RoomDenSummary": {
"type": "object",
"required": [
@@ -1140,6 +1493,39 @@
}
}
},
+ "RoomKitchenForGuests": {
+ "type": "object",
+ "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": [
+ "Kitchen"
+ ]
+ }
+ }
+ },
"RoomKitchenSummary": {
"type": "object",
"required": [
@@ -1190,6 +1576,32 @@
}
}
},
+ "RoomLivingRoomForGuests": {
+ "type": "object",
+ "required": [
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "LivingRoom"
+ ]
+ }
+ }
+ },
"RoomLivingRoomSummary": {
"type": "object",
"required": [
diff --git a/api/src/openapi/tests.openapi.json b/api/src/openapi/tests.openapi.json
index da67510..d6e4ca9 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,71 @@
},
"components": {
"schemas": {
+ "Appliance": {
+ "type": "object",
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "dishwasher",
+ "microwave",
+ "oven",
+ "stove"
+ ]
+ }
+ }
+ },
+ "BathOrShowerStyle": {
+ "type": "object",
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "bath",
+ "bath_and_shower",
+ "none",
+ "shower"
+ ]
+ }
+ }
+ },
+ "BedType": {
+ "type": "object",
+ "required": [
+ "label",
+ "value"
+ ],
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "bunk",
+ "cot",
+ "king",
+ "queen",
+ "sofabed",
+ "twin"
+ ]
+ }
+ }
+ },
"OpenapiValidationErrors": {
"type": "object",
"required": [
@@ -908,6 +1098,65 @@
}
}
},
+ "PlaceForGuests": {
+ "type": "object",
+ "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",
"required": [
@@ -923,6 +1172,21 @@
}
}
},
+ "PlaceSummaryForGuests": {
+ "type": "object",
+ "required": [
+ "id",
+ "title"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ }
+ }
+ },
"RoomBathroom": {
"type": "object",
"required": [
@@ -962,6 +1226,36 @@
}
}
},
+ "RoomBathroomForGuests": {
+ "type": "object",
+ "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"
+ ]
+ }
+ }
+ },
"RoomBathroomSummary": {
"type": "object",
"required": [
@@ -1027,6 +1321,39 @@
}
}
},
+ "RoomBedroomForGuests": {
+ "type": "object",
+ "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": [
+ "Bedroom"
+ ]
+ }
+ }
+ },
"RoomBedroomSummary": {
"type": "object",
"required": [
@@ -1077,6 +1404,32 @@
}
}
},
+ "RoomDenForGuests": {
+ "type": "object",
+ "required": [
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "Den"
+ ]
+ }
+ }
+ },
"RoomDenSummary": {
"type": "object",
"required": [
@@ -1140,6 +1493,39 @@
}
}
},
+ "RoomKitchenForGuests": {
+ "type": "object",
+ "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": [
+ "Kitchen"
+ ]
+ }
+ }
+ },
"RoomKitchenSummary": {
"type": "object",
"required": [
@@ -1190,6 +1576,32 @@
}
}
},
+ "RoomLivingRoomForGuests": {
+ "type": "object",
+ "required": [
+ "displayType",
+ "id",
+ "title",
+ "type"
+ ],
+ "properties": {
+ "displayType": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "LivingRoom"
+ ]
+ }
+ }
+ },
"RoomLivingRoomSummary": {
"type": "object",
"required": [
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 d73f541..747997b 100644
--- a/api/src/types/dream.ts
+++ b/api/src/types/dream.ts
@@ -456,7 +456,7 @@ export const schema = {
},
},
places: {
- serializerKeys: ['default', 'summary'],
+ serializerKeys: ['default', 'forGuests', 'summary', 'summaryForGuests'],
scopes: {
default: ['dream:SoftDelete'],
named: [],
@@ -585,7 +585,7 @@ export const schema = {
},
},
rooms: {
- serializerKeys: ['default', 'summary'],
+ serializerKeys: ['default', 'forGuests', 'summary'],
scopes: {
default: ['dream:STI', 'dream:SoftDelete'],
named: [],
diff --git a/api/src/types/openapi/tests.openapi.d.ts b/api/src/types/openapi/tests.openapi.d.ts
index dc147fa..d0e8a21 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";
@@ -490,10 +604,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;
@@ -502,6 +629,14 @@ export interface components {
/** @enum {string} */
type: "Bathroom";
};
+ RoomBathroomForGuests: {
+ bathOrShowerStyle: components["schemas"]["BathOrShowerStyle"];
+ displayType: string;
+ id: string;
+ title: string;
+ /** @enum {string} */
+ type: "Bathroom";
+ };
RoomBathroomSummary: {
id: string;
position: number | null;
@@ -515,6 +650,14 @@ export interface components {
/** @enum {string} */
type: "Bedroom";
};
+ RoomBedroomForGuests: {
+ bedTypes: components["schemas"]["BedType"][];
+ displayType: string;
+ id: string;
+ title: string;
+ /** @enum {string} */
+ type: "Bedroom";
+ };
RoomBedroomSummary: {
id: string;
position: number | null;
@@ -527,6 +670,13 @@ export interface components {
/** @enum {string} */
type: "Den";
};
+ RoomDenForGuests: {
+ displayType: string;
+ id: string;
+ title: string;
+ /** @enum {string} */
+ type: "Den";
+ };
RoomDenSummary: {
id: string;
position: number | null;
@@ -540,6 +690,14 @@ export interface components {
/** @enum {string} */
type: "Kitchen";
};
+ RoomKitchenForGuests: {
+ appliances: components["schemas"]["Appliance"][];
+ displayType: string;
+ id: string;
+ title: string;
+ /** @enum {string} */
+ type: "Kitchen";
+ };
RoomKitchenSummary: {
id: string;
position: number | null;
@@ -552,6 +710,13 @@ export interface components {
/** @enum {string} */
type: "LivingRoom";
};
+ RoomLivingRoomForGuests: {
+ displayType: string;
+ id: string;
+ title: string;
+ /** @enum {string} */
+ type: "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')