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..862bb32 100644
--- a/api/spec/unit/controllers/V1/Host/PlacesController.spec.ts
+++ b/api/spec/unit/controllers/V1/Host/PlacesController.spec.ts
@@ -1,9 +1,10 @@
+import Host from '@models/Host.js'
import Place from '@models/Place.js'
import User from '@models/User.js'
-import Host from '@models/Host.js'
+import createHost from '@spec/factories/HostFactory.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'
import { RequestBody, session, SpecRequestType } from '@spec/unit/helpers/authentication.js'

describe('V1/Host/PlacesController', () => {
@@ -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 9ecb9f2..7f37ec2 100644
--- a/api/src/app/controllers/AuthedController.ts
+++ b/api/src/app/controllers/AuthedController.ts
@@ -1,30 +1,27 @@
import AppEnv from '@conf/AppEnv.js'
import ApplicationController from '@controllers/ApplicationController.js'
+import User from '@models/User.js'
import { Encrypt } from '@rvoh/dream/utils'
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() {
- /** uncomment after creating User model */
- // const userId = this.authedUserId()
- // if (!userId) return this.unauthorized()
- //
- // const user = await User.find(userId)
- // if (!user) return this.unauthorized()
- //
- // this.currentUser = user
+ const userId = this.authedUserId()
+ if (!userId) return this.unauthorized()
+
+ const user = await User.find(userId)
+ if (!user) return this.unauthorized()
+
+ this.currentUser = user
}

protected authedUserId(): string | 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 = (this.header('authorization') ?? '').split(' ').at(-1)!
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 f6b0e42..ee34669 100644
--- a/api/src/app/controllers/V1/Host/PlacesController.ts
+++ b/api/src/app/controllers/V1/Host/PlacesController.ts
@@ -1,6 +1,8 @@
+import ApplicationModel from '@models/ApplicationModel.js'
+import HostPlace from '@models/HostPlace.js'
+import Place from '@models/Place.js'
import { OpenAPI } from '@rvoh/psychic'
import V1HostBaseController from './BaseController.js'
-import Place from '@models/Place.js'

const openApiTags = ['places']

@@ -14,10 +16,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, {
@@ -27,8 +30,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, {
@@ -38,9 +41,14 @@ export default class V1HostPlacesController extends V1HostBaseController {
fastJsonStringify: true,
})
public async create() {
- // let place = await this.currentHost.createAssociation('places', this.paramsFor(Place))
- // 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.paramsFor(Place))
+ 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, {
@@ -50,9 +58,9 @@ export default class V1HostPlacesController extends V1HostBaseController {
fastJsonStringify: true,
})
public async update() {
- // const place = await this.place()
- // await place.update(this.paramsFor(Place))
- // this.noContent()
+ const place = await this.place()
+ await place.update(this.paramsFor(Place))
+ this.noContent()
}

@OpenAPI({
@@ -62,14 +70,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', 'string'))
}
}