Skip to main content

PlacesController specs passing

Commit Message

PlacesController specs passing

NOTE: if, after running `pnpm psy sync`, your editor starts
showing errors like `Unsafe call of a(n) 'error' type typed value`,
restart the ESLint server (the `ESLint: Restart ESLint Server`
command in VSCode / Cursor)

```console
pnpm uspec spec/unit/controllers/V1/Host/PlacesController.spec.ts
# if it hangs, it's likely that you haven't implemented the controller
# changes; simply adding `this.ok()` or `this.noContent()` to each action
# in the controller will get the spec run to complete
```

Changes

diff --git a/api/spec/unit/controllers/V1/Host/PlacesController.spec.ts b/api/spec/unit/controllers/V1/Host/PlacesController.spec.ts
index 64923d4..830ef03 100644
--- a/api/spec/unit/controllers/V1/Host/PlacesController.spec.ts
+++ b/api/spec/unit/controllers/V1/Host/PlacesController.spec.ts
@@ -1,6 +1,7 @@
import Place from '@models/Place.js'
import User from '@models/User.js'
import Host from '@models/Host.js'
+import createHostPlace from '@spec/factories/HostPlaceFactory.js'
import createPlace from '@spec/factories/PlaceFactory.js'
import createUser from '@spec/factories/UserFactory.js'
import createHost from '@spec/factories/HostFactory.js'
@@ -23,7 +24,8 @@ describe('V1/Host/PlacesController', () => {
}

it('returns the index of Places', async () => {
- const place = await createPlace({ host })
+ const place = await createPlace()
+ await createHostPlace({ host, place })

const { body } = await index(200)

@@ -53,7 +55,8 @@ describe('V1/Host/PlacesController', () => {
}

it('returns the specified Place', async () => {
- const place = await createPlace({ host })
+ const place = await createPlace()
+ await createHostPlace({ host, place })

const { body } = await show(place, 200)

@@ -122,7 +125,8 @@ describe('V1/Host/PlacesController', () => {
}

it('updates the Place', async () => {
- const place = await createPlace({ host })
+ const place = await createPlace()
+ await createHostPlace({ host, place })

await update(place, {
name: 'Updated Place name',
@@ -165,7 +169,8 @@ describe('V1/Host/PlacesController', () => {
}

it('deletes the Place', async () => {
- const place = await createPlace({ host })
+ const place = await createPlace()
+ await createHostPlace({ host, place })

await destroy(place, 204)

diff --git a/api/src/app/controllers/AuthedController.ts b/api/src/app/controllers/AuthedController.ts
index 9714b0f..9bd17df 100644
--- a/api/src/app/controllers/AuthedController.ts
+++ b/api/src/app/controllers/AuthedController.ts
@@ -1,18 +1,15 @@
import ApplicationController from '@controllers/ApplicationController.js'
import resolveCurrentUser from '@controllers/helpers/resolveCurrentUser.js'
+import User from '@models/User.js'
import { BeforeAction } from '@rvoh/psychic'
-/** uncomment after creating User model */
-// import User from '@models/User.js'

export default class AuthedController extends ApplicationController {
- /** uncomment after creating User model */
- // protected currentUser: User
+ protected currentUser: User

@BeforeAction()
protected async authenticate() {
const user = await resolveCurrentUser(this)
if (!user) return this.unauthorized()
- /** uncomment after creating User model */
- // this.currentUser = user
+ this.currentUser = user
}
}
diff --git a/api/src/app/controllers/V1/Host/BaseController.ts b/api/src/app/controllers/V1/Host/BaseController.ts
index 1a200d0..1f537e5 100644
--- a/api/src/app/controllers/V1/Host/BaseController.ts
+++ b/api/src/app/controllers/V1/Host/BaseController.ts
@@ -1,5 +1,15 @@
+import Host from '@models/Host.js'
+import { BeforeAction } from '@rvoh/psychic'
import V1BaseController from '../BaseController.js'

export default class V1HostBaseController extends V1BaseController {
+ protected currentHost: Host

+ @BeforeAction()
+ protected async loadCurrentHost() {
+ const host = await this.currentUser.associationQuery('host').first()
+ if (!host) return this.forbidden()
+
+ this.currentHost = host
+ }
}
diff --git a/api/src/app/controllers/V1/Host/PlacesController.ts b/api/src/app/controllers/V1/Host/PlacesController.ts
index e3399a7..ebafde5 100644
--- a/api/src/app/controllers/V1/Host/PlacesController.ts
+++ b/api/src/app/controllers/V1/Host/PlacesController.ts
@@ -1,5 +1,7 @@
import { OpenAPI } from '@rvoh/psychic'
import { DreamParamSafeColumnNames } from '@rvoh/dream/types'
+import ApplicationModel from '@models/ApplicationModel.js'
+import HostPlace from '@models/HostPlace.js'
import V1HostBaseController from './BaseController.js'
import Place from '@models/Place.js'

@@ -17,10 +19,11 @@ export default class V1HostPlacesController extends V1HostBaseController {
fastJsonStringify: true,
})
public async index() {
- // const places = await this.currentHost.associationQuery('places')
- // .preloadFor('summary')
- // .cursorPaginate({ cursor: this.castParam('cursor', 'string', { allowNull: true }) })
- // this.ok(places)
+ const places = await this.currentHost
+ .associationQuery('places')
+ .preloadFor('summary')
+ .cursorPaginate({ cursor: this.castParam('cursor', 'string', { allowNull: true }) })
+ this.ok(places)
}

@OpenAPI(Place, {
@@ -30,8 +33,8 @@ export default class V1HostPlacesController extends V1HostBaseController {
fastJsonStringify: true,
})
public async show() {
- // const place = await this.place()
- // this.ok(place)
+ const place = await this.place()
+ this.ok(place)
}

@OpenAPI(Place, {
@@ -44,9 +47,14 @@ export default class V1HostPlacesController extends V1HostBaseController {
},
})
public async create() {
- // let place = await this.currentHost.createAssociation('places', this.extractParams(Place, paramSafeColumns))
- // if (place.isPersisted) place = await place.loadFor('default').execute()
- // this.created(place)
+ let place = await ApplicationModel.transaction(async txn => {
+ const place = await Place.txn(txn).create(this.extractParams(Place, paramSafeColumns))
+ await HostPlace.txn(txn).create({ host: this.currentHost, place })
+ return place
+ })
+
+ if (place.isPersisted) place = await place.loadFor('default').execute()
+ this.created(place)
}

@OpenAPI(Place, {
@@ -59,9 +67,9 @@ export default class V1HostPlacesController extends V1HostBaseController {
},
})
public async update() {
- // const place = await this.place()
- // await place.update(this.extractParams(Place, paramSafeColumns))
- // this.noContent()
+ const place = await this.place()
+ await place.update(this.extractParams(Place, paramSafeColumns))
+ this.noContent()
}

@OpenAPI({
@@ -71,14 +79,15 @@ export default class V1HostPlacesController extends V1HostBaseController {
fastJsonStringify: true,
})
public async destroy() {
- // const place = await this.place()
- // await place.destroy()
- // this.noContent()
+ const place = await this.place()
+ await place.destroy()
+ this.noContent()
}

private async place() {
- // return await this.currentHost.associationQuery('places')
- // .preloadFor('default')
- // .findOrFail(this.castParam('id', 'string'))
+ return await this.currentHost
+ .associationQuery('places')
+ .preloadFor('default')
+ .findOrFail(this.castParam('id', 'uuid'))
}
}
diff --git a/api/src/app/controllers/helpers/resolveCurrentUser.ts b/api/src/app/controllers/helpers/resolveCurrentUser.ts
index 0d6f1aa..24084aa 100644
--- a/api/src/app/controllers/helpers/resolveCurrentUser.ts
+++ b/api/src/app/controllers/helpers/resolveCurrentUser.ts
@@ -1,16 +1,12 @@
import AppEnv from '@conf/AppEnv.js'
+import User from '@models/User.js'
import { Encrypt } from '@rvoh/dream/utils'
import { PsychicController } from '@rvoh/psychic'
-/** uncomment after creating User model */
-// import User from '@models/User.js'

-// eslint-disable-next-line @typescript-eslint/require-await
-export default async function resolveCurrentUser(controller: PsychicController): Promise<string | null> {
- /** replace previous line with uncommented next line after creating User model */
- // export default async function resolveCurrentUser(controller: PsychicController): Promise<User | null> {
+export default async function resolveCurrentUser(controller: PsychicController): Promise<User | null> {
if (!AppEnv.isTest)
throw new Error(
- 'The current authentication scheme is only for early development. Replace with a production grade authentication scheme.'
+ 'The current authentication scheme is only for early development. Replace with a production grade authentication scheme.',
)

const token = (controller.header('authorization') ?? '').split(' ').at(-1)!
@@ -20,11 +16,8 @@ export default async function resolveCurrentUser(controller: PsychicController):
key: AppEnv.string('APP_ENCRYPTION_KEY'),
})

- const userId =
- typeof decrypted === 'string' && (JSON.parse(decrypted) as Record<'userId', string>)?.userId
+ const userId = typeof decrypted === 'string' && (JSON.parse(decrypted) as Record<'userId', string>)?.userId
if (!userId) return null

- /** uncomment after creating User model */
- // return await User.find(userId)
- return userId
+ return await User.find(userId)
}