diff --git a/api/spec/factories/Room/BedroomFactory.ts b/api/spec/factories/Room/BedroomFactory.ts
new file mode 100644
index 0000000..354c9e9
--- /dev/null
+++ b/api/spec/factories/Room/BedroomFactory.ts
@@ -0,0 +1,9 @@
+import { UpdateableProperties } from '@rvoh/dream/types'
+import Bedroom from '@models/Room/Bedroom.js'
+
+export default async function createBedroom(attrs: UpdateableProperties<Bedroom> = {}) {
+ return await Bedroom.create({
+ bedTypes: ['twin'],
+ ...attrs,
+ })
+}
diff --git a/api/spec/unit/models/Room/Bedroom.spec.ts b/api/spec/unit/models/Room/Bedroom.spec.ts
new file mode 100644
index 0000000..89fb496
--- /dev/null
+++ b/api/spec/unit/models/Room/Bedroom.spec.ts
@@ -0,0 +1,3 @@
+describe('Room/Bedroom', () => {
+ it.todo('add a test here to get started building Room/Bedroom')
+})
diff --git a/api/src/app/models/Room/Bedroom.ts b/api/src/app/models/Room/Bedroom.ts
new file mode 100644
index 0000000..ed0eeb4
--- /dev/null
+++ b/api/src/app/models/Room/Bedroom.ts
@@ -0,0 +1,19 @@
+import { STI } from '@rvoh/dream'
+import { DreamColumn, DreamSerializers } from '@rvoh/dream/types'
+import Room from '@models/Room.js'
+
+// Uncomment when adding decorators (@deco.BelongsTo, @deco.Validates, etc.):
+// import { Decorators } from '@rvoh/dream'
+// const deco = new Decorators<typeof Bedroom>()
+
+@STI(Room)
+export default class Bedroom extends Room {
+ public override get serializers(): DreamSerializers<Bedroom> {
+ return {
+ default: 'Room/BedroomSerializer',
+ summary: 'Room/BedroomSummarySerializer',
+ }
+ }
+
+ public bedTypes: DreamColumn<Bedroom, 'bedTypes'>
+}
diff --git a/api/src/app/serializers/Room/BedroomSerializer.ts b/api/src/app/serializers/Room/BedroomSerializer.ts
new file mode 100644
index 0000000..f34b5b4
--- /dev/null
+++ b/api/src/app/serializers/Room/BedroomSerializer.ts
@@ -0,0 +1,9 @@
+import { RoomSerializer, RoomSummarySerializer } from '@serializers/RoomSerializer.js'
+import Bedroom from '@models/Room/Bedroom.js'
+
+export const RoomBedroomSummarySerializer = (bedroom: Bedroom) =>
+ RoomSummarySerializer(Bedroom, bedroom)
+
+export const RoomBedroomSerializer = (bedroom: Bedroom) =>
+ RoomSerializer(Bedroom, bedroom)
+ .attribute('bedTypes')
diff --git a/api/src/db/migrations/1782836036208-create-room-bedroom.ts b/api/src/db/migrations/1782836036208-create-room-bedroom.ts
new file mode 100644
index 0000000..55d5d4c
--- /dev/null
+++ b/api/src/db/migrations/1782836036208-create-room-bedroom.ts
@@ -0,0 +1,31 @@
+import { Kysely, sql } from 'kysely'
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export async function up(db: Kysely<any>): Promise<void> {
+ await db.schema
+ .createType('bed_types_enum')
+ .asEnum([
+ 'twin',
+ 'bunk',
+ 'queen',
+ 'king',
+ 'cot',
+ 'sofabed'
+ ])
+ .execute()
+
+ await db.schema
+ .alterTable('rooms')
+ .addColumn('bed_types', sql`bed_types_enum[]`, col => col.notNull().defaultTo('{}'))
+ .execute()
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export async function down(db: Kysely<any>): Promise<void> {
+ await db.schema
+ .alterTable('rooms')
+ .dropColumn('bed_types')
+ .execute()
+
+ await db.schema.dropType('bed_types_enum').execute()
+}
\ No newline at end of file
diff --git a/api/src/openapi/mobile.openapi.json b/api/src/openapi/mobile.openapi.json
index 4b065cd..ba0046f 100644
--- a/api/src/openapi/mobile.openapi.json
+++ b/api/src/openapi/mobile.openapi.json
@@ -328,7 +328,14 @@
"results": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/RoomBathroomSummary"
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/RoomBathroomSummary"
+ },
+ {
+ "$ref": "#/components/schemas/RoomBedroomSummary"
+ }
+ ]
}
}
}
@@ -384,7 +391,14 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/RoomBathroom"
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/RoomBathroom"
+ },
+ {
+ "$ref": "#/components/schemas/RoomBedroom"
+ }
+ ]
}
}
},
@@ -440,7 +454,14 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/RoomBathroom"
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/RoomBathroom"
+ },
+ {
+ "$ref": "#/components/schemas/RoomBedroom"
+ }
+ ]
}
}
},
@@ -683,6 +704,48 @@
}
}
},
+ "RoomBedroom": {
+ "type": "object",
+ "required": [
+ "bedTypes",
+ "id",
+ "position",
+ "type"
+ ],
+ "properties": {
+ "bedTypes": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "description": "The following values will be allowed:\n bunk,\n cot,\n king,\n queen,\n sofabed,\n twin"
+ }
+ },
+ "id": {
+ "type": "string"
+ },
+ "position": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "type": {
+ "type": "string",
+ "description": "The following values will be allowed:\n Bedroom"
+ }
+ }
+ },
+ "RoomBedroomSummary": {
+ "type": "object",
+ "required": [
+ "id"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ }
+ }
+ },
"ValidationErrors": {
"type": "object",
"required": [
diff --git a/api/src/openapi/openapi.json b/api/src/openapi/openapi.json
index 3bf27f4..335ce67 100644
--- a/api/src/openapi/openapi.json
+++ b/api/src/openapi/openapi.json
@@ -328,7 +328,14 @@
"results": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/RoomBathroomSummary"
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/RoomBathroomSummary"
+ },
+ {
+ "$ref": "#/components/schemas/RoomBedroomSummary"
+ }
+ ]
}
}
}
@@ -384,7 +391,14 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/RoomBathroom"
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/RoomBathroom"
+ },
+ {
+ "$ref": "#/components/schemas/RoomBedroom"
+ }
+ ]
}
}
},
@@ -440,7 +454,14 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/RoomBathroom"
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/RoomBathroom"
+ },
+ {
+ "$ref": "#/components/schemas/RoomBedroom"
+ }
+ ]
}
}
},
@@ -699,6 +720,57 @@
}
}
},
+ "RoomBedroom": {
+ "type": "object",
+ "required": [
+ "bedTypes",
+ "id",
+ "position",
+ "type"
+ ],
+ "properties": {
+ "bedTypes": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "bunk",
+ "cot",
+ "king",
+ "queen",
+ "sofabed",
+ "twin"
+ ]
+ }
+ },
+ "id": {
+ "type": "string"
+ },
+ "position": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "Bedroom"
+ ]
+ }
+ }
+ },
+ "RoomBedroomSummary": {
+ "type": "object",
+ "required": [
+ "id"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ }
+ }
+ },
"ValidationErrors": {
"type": "object",
"required": [
diff --git a/api/src/openapi/tests.openapi.json b/api/src/openapi/tests.openapi.json
index 7d5e68b..b8a904a 100644
--- a/api/src/openapi/tests.openapi.json
+++ b/api/src/openapi/tests.openapi.json
@@ -328,7 +328,14 @@
"results": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/RoomBathroomSummary"
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/RoomBathroomSummary"
+ },
+ {
+ "$ref": "#/components/schemas/RoomBedroomSummary"
+ }
+ ]
}
}
}
@@ -384,7 +391,14 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/RoomBathroom"
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/RoomBathroom"
+ },
+ {
+ "$ref": "#/components/schemas/RoomBedroom"
+ }
+ ]
}
}
},
@@ -440,7 +454,14 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/RoomBathroom"
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/RoomBathroom"
+ },
+ {
+ "$ref": "#/components/schemas/RoomBedroom"
+ }
+ ]
}
}
},
@@ -699,6 +720,57 @@
}
}
},
+ "RoomBedroom": {
+ "type": "object",
+ "required": [
+ "bedTypes",
+ "id",
+ "position",
+ "type"
+ ],
+ "properties": {
+ "bedTypes": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "bunk",
+ "cot",
+ "king",
+ "queen",
+ "sofabed",
+ "twin"
+ ]
+ }
+ },
+ "id": {
+ "type": "string"
+ },
+ "position": {
+ "type": [
+ "integer",
+ "null"
+ ]
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "Bedroom"
+ ]
+ }
+ }
+ },
+ "RoomBedroomSummary": {
+ "type": "object",
+ "required": [
+ "id"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ }
+ }
+ },
"ValidationErrors": {
"type": "object",
"required": [
diff --git a/api/src/types/db.ts b/api/src/types/db.ts
index abf0c14..2e7578c 100644
--- a/api/src/types/db.ts
+++ b/api/src/types/db.ts
@@ -68,6 +68,14 @@ import {
*/
import type { ColumnType } from 'kysely'
+export type ArrayType<T> =
+ ArrayTypeImpl<T> extends (infer U)[] ? U[] : ArrayTypeImpl<T>
+
+export type ArrayTypeImpl<T> =
+ T extends ColumnType<infer S, infer I, infer U>
+ ? ColumnType<S[], I[], U[]>
+ : T[]
+
export type BathOrShowerStylesEnum =
'bath' | 'bath_and_shower' | 'none' | 'shower'
@@ -78,6 +86,18 @@ export const BathOrShowerStylesEnumValues = [
'shower',
] as const
+export type BedTypesEnum =
+ 'bunk' | 'cot' | 'king' | 'queen' | 'sofabed' | 'twin'
+
+export const BedTypesEnumValues = [
+ 'bunk',
+ 'cot',
+ 'king',
+ 'queen',
+ 'sofabed',
+ 'twin',
+] as const
+
export type Generated<T> =
T extends ColumnType<infer S, infer I, infer U>
? ColumnType<S, I | undefined, U>
@@ -146,6 +166,7 @@ export interface Places {
export interface Rooms {
bathOrShowerStyle: BathOrShowerStylesEnum | null
+ bedTypes: Generated<ArrayType<BedTypesEnum>>
createdAt: Timestamp
deletedAt: Timestamp | null
id: Generated<string>
diff --git a/api/src/types/dream.globals.ts b/api/src/types/dream.globals.ts
index b4348f8..6109591 100644
--- a/api/src/types/dream.globals.ts
+++ b/api/src/types/dream.globals.ts
@@ -66,6 +66,8 @@ export const globalTypeConfig = {
'PlaceSummarySerializer',
'Room/BathroomSerializer',
'Room/BathroomSummarySerializer',
+ 'Room/BedroomSerializer',
+ 'Room/BedroomSummarySerializer',
'RoomSerializer',
'RoomSummarySerializer',
],
diff --git a/api/src/types/dream.ts b/api/src/types/dream.ts
index e450fe4..47fd445 100644
--- a/api/src/types/dream.ts
+++ b/api/src/types/dream.ts
@@ -64,9 +64,11 @@ import {
} from '@rvoh/dream'
import {
type BathOrShowerStylesEnum,
+ type BedTypesEnum,
type PlaceStylesEnum,
type RoomTypesEnum,
BathOrShowerStylesEnumValues,
+ BedTypesEnumValues,
PlaceStylesEnumValues,
RoomTypesEnumValues,
} from './db.js'
@@ -426,6 +428,7 @@ export const schema = {
},
nonJsonColumnNames: [
'bathOrShowerStyle',
+ 'bedTypes',
'createdAt',
'deletedAt',
'id',
@@ -444,6 +447,15 @@ export const schema = {
allowNull: true,
isArray: false,
},
+ bedTypes: {
+ coercedType: {} as BedTypesEnum[],
+ enumType: {} as BedTypesEnum,
+ enumArrayType: [] as BedTypesEnum[],
+ enumValues: BedTypesEnumValues,
+ dbType: 'bed_types_enum[]',
+ allowNull: false,
+ isArray: true,
+ },
createdAt: {
coercedType: {} as DateTime,
enumType: null,
@@ -626,6 +638,7 @@ export const connectionTypeConfig = {
Place: 'places',
Room: 'rooms',
'Room/Bathroom': 'rooms',
+ 'Room/Bedroom': 'rooms',
User: 'users',
},
},
diff --git a/api/src/types/openapi/tests.openapi.d.ts b/api/src/types/openapi/tests.openapi.d.ts
index d2a2e3a..734f1ba 100644
--- a/api/src/types/openapi/tests.openapi.d.ts
+++ b/api/src/types/openapi/tests.openapi.d.ts
@@ -220,7 +220,7 @@ export interface paths {
content: {
"application/json": {
cursor: string | null;
- results: components["schemas"]["RoomBathroomSummary"][];
+ results: (components["schemas"]["RoomBathroomSummary"] | components["schemas"]["RoomBedroomSummary"])[];
};
};
};
@@ -260,7 +260,7 @@ export interface paths {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["RoomBathroom"];
+ "application/json": components["schemas"]["RoomBathroom"] | components["schemas"]["RoomBedroom"];
};
};
400: components["responses"]["BadRequest"];
@@ -306,7 +306,7 @@ export interface paths {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["RoomBathroom"];
+ "application/json": components["schemas"]["RoomBathroom"] | components["schemas"]["RoomBedroom"];
};
};
400: components["responses"]["BadRequest"];
@@ -414,6 +414,16 @@ export interface components {
RoomBathroomSummary: {
id: string;
};
+ RoomBedroom: {
+ bedTypes: ("bunk" | "cot" | "king" | "queen" | "sofabed" | "twin")[];
+ id: string;
+ position: number | null;
+ /** @enum {string} */
+ type: "Bedroom";
+ };
+ RoomBedroomSummary: {
+ id: string;
+ };
ValidationErrors: {
/** @enum {string} */
type: "validation";