Skip to main content

User HasOne Host

Commit Message

User HasOne Host

Users do _not_ automatically get a Host (special onboarding process for
becoming a host).
Each User can have at most one Host (unique index on user_id foreign key).
Sync association types.

```console
pnpm psy db:migrate
pnpm uspec spec/unit/models
```

If migrations had already been run, after adding
a new association, one could run `sync` instead
(`db:migrate` runs `sync` implicitly):

```console
pnpm psy sync
```

Changes

diff --git a/api/src/app/models/User.ts b/api/src/app/models/User.ts
index 9b05776..55cf5f8 100644
--- a/api/src/app/models/User.ts
+++ b/api/src/app/models/User.ts
@@ -2,6 +2,7 @@ import { Decorators, SoftDelete } from '@rvoh/dream'
import { DreamColumn } from '@rvoh/dream/types'
import ApplicationModel from '@models/ApplicationModel.js'
import Guest from '@models/Guest.js'
+import Host from '@models/Host.js'

const deco = new Decorators<typeof User>()

@@ -27,4 +28,7 @@ export default class User extends ApplicationModel {

@deco.HasOne('Guest')
public guest: Guest
+
+ @deco.HasOne('Host')
+ public host: Host
}
diff --git a/api/src/db/migrations/1779136307015-create-host.ts b/api/src/db/migrations/1779136307015-create-host.ts
index edc2a99..70cf71f 100644
--- a/api/src/db/migrations/1779136307015-create-host.ts
+++ b/api/src/db/migrations/1779136307015-create-host.ts
@@ -9,7 +9,7 @@ export async function up(db: Kysely<any>): Promise<void> {
.primaryKey()
.defaultTo(sql`uuidv7()`),
)
- .addColumn('user_id', 'uuid', col => col.references('users.id').onDelete('restrict').notNull())
+ .addColumn('user_id', 'uuid', col => col.references('users.id').onDelete('restrict').notNull().unique())
.addColumn('created_at', 'timestamp', col => col.notNull())
.addColumn('updated_at', 'timestamp', col => col.notNull())
.addColumn('deleted_at', 'timestamp')
@@ -26,4 +26,4 @@ export async function up(db: Kysely<any>): Promise<void> {
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropIndex('hosts_user_id').execute()
await db.schema.dropTable('hosts').execute()
-}
\ No newline at end of file
+}
diff --git a/api/src/types/db.ts b/api/src/types/db.ts
index bd4ea0f..25bb48d 100644
--- a/api/src/types/db.ts
+++ b/api/src/types/db.ts
@@ -83,6 +83,14 @@ export interface Guests {
userId: string
}

+export interface Hosts {
+ createdAt: Timestamp
+ deletedAt: Timestamp | null
+ id: Generated<string>
+ updatedAt: Timestamp
+ userId: string
+}
+
export interface Users {
createdAt: Timestamp
deletedAt: Timestamp | null
@@ -94,10 +102,12 @@ export interface Users {

export interface DB {
guests: Guests
+ hosts: Hosts
users: Users
}

export class DBClass {
guests: Guests
+ hosts: Hosts
users: Users
}
diff --git a/api/src/types/dream.globals.ts b/api/src/types/dream.globals.ts
index 6c9f10f..f241f3d 100644
--- a/api/src/types/dream.globals.ts
+++ b/api/src/types/dream.globals.ts
@@ -57,5 +57,10 @@ us humans, he says:
*/

export const globalTypeConfig = {
- serializers: ['GuestSerializer', 'GuestSummarySerializer'],
+ serializers: [
+ 'GuestSerializer',
+ 'GuestSummarySerializer',
+ 'HostSerializer',
+ 'HostSummarySerializer',
+ ],
} as const
diff --git a/api/src/types/dream.ts b/api/src/types/dream.ts
index b5bb80d..9c8cc4b 100644
--- a/api/src/types/dream.ts
+++ b/api/src/types/dream.ts
@@ -132,6 +132,73 @@ export const schema = {
},
},
},
+ hosts: {
+ serializerKeys: ['default', 'summary'],
+ scopes: {
+ default: ['dream:SoftDelete'],
+ named: [],
+ },
+ nonJsonColumnNames: ['createdAt', 'deletedAt', 'id', 'updatedAt', 'userId'],
+ columns: {
+ createdAt: {
+ coercedType: {} as DateTime,
+ enumType: null,
+ enumArrayType: null,
+ enumValues: null,
+ dbType: 'timestamp without time zone',
+ allowNull: false,
+ isArray: false,
+ },
+ deletedAt: {
+ coercedType: {} as DateTime | null,
+ enumType: null,
+ enumArrayType: null,
+ enumValues: null,
+ dbType: 'timestamp without time zone',
+ allowNull: true,
+ isArray: false,
+ },
+ id: {
+ coercedType: {} as string,
+ enumType: null,
+ enumArrayType: null,
+ enumValues: null,
+ dbType: 'uuid',
+ allowNull: false,
+ isArray: false,
+ },
+ updatedAt: {
+ coercedType: {} as DateTime,
+ enumType: null,
+ enumArrayType: null,
+ enumValues: null,
+ dbType: 'timestamp without time zone',
+ allowNull: false,
+ isArray: false,
+ },
+ userId: {
+ coercedType: {} as string,
+ enumType: null,
+ enumArrayType: null,
+ enumValues: null,
+ dbType: 'uuid',
+ allowNull: false,
+ isArray: false,
+ },
+ },
+ virtualColumns: [],
+ associations: {
+ user: {
+ type: 'BelongsTo',
+ foreignKey: 'userId',
+ foreignKeyTypeColumn: null,
+ tables: ['users'],
+ optional: false,
+ requiredAndClauses: null,
+ passthroughAndClauses: null,
+ },
+ },
+ },
users: {
serializerKeys: [],
scopes: {
@@ -213,6 +280,15 @@ export const schema = {
requiredAndClauses: null,
passthroughAndClauses: null,
},
+ host: {
+ type: 'HasOne',
+ foreignKey: 'userId',
+ foreignKeyTypeColumn: null,
+ tables: ['hosts'],
+ optional: null,
+ requiredAndClauses: null,
+ passthroughAndClauses: null,
+ },
},
},
} as const
@@ -223,6 +299,7 @@ export const connectionTypeConfig = {
globalNames: {
models: {
Guest: 'guests',
+ Host: 'hosts',
User: 'users',
},
},