Injecting HttpContext inside a service doesn't work #4371
-
I need to create a service that will be called within a model hook. In this service I need access to HttpContext in order to get the current authenticated user. I was referring to this section to create my service - https://docs.adonisjs.com/guides/ioc-container#constructing-a-tree-of-dependencies Here's my model: import { DateTime } from 'luxon'
import { BaseModel, column, hasMany, belongsTo, beforeCreate } from '@adonisjs/lucid/orm'
import CamelCaseNamingStrategy from '#strategies/camel_case_naming_strategy'
import type { BelongsTo, HasMany } from '@adonisjs/lucid/types/relations'
import app from '@adonisjs/core/services/app'
import ListProperty from '#models/list_property'
import Workspace from '#models/workspace'
import WorkspaceService from '#services/workspace_service'
export default class List extends BaseModel {
public static namingStrategy = new CamelCaseNamingStrategy()
@hasMany(() => ListProperty)
declare properties: HasMany<typeof ListProperty>
@column({ isPrimary: true })
declare id: number
@column()
declare workspaceId: number
@belongsTo(() => Workspace)
declare workspace: BelongsTo<typeof Workspace>
@column()
declare name: string
@column()
declare source: string
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime
@beforeCreate()
static async setWorkspace(list: List) {
const service = await app.container.make(WorkspaceService)
await service.handle(list)
}
} And this is my service: import List from '#models/list'
import { inject } from '@adonisjs/core'
import { HttpContext } from '@adonisjs/core/http'
@inject()
export default class WorkspaceService {
constructor(protected ctx: HttpContext) {
}
async handle (list: List) {
const user = this.ctx.auth.user
if (user && user.workspaces.length > 0) {
await list.related('workspace').associate(user.workspaces[0])
}
}
} But when the What am I doing wrong? I am using v6. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
This doesn't directly answer your question, but are you sure you're able to associate a
|
Beta Was this translation helpful? Give feedback.
-
Yes this does not work as expected. Because you are using the Creating model hooks that relies on HTTP request is not really a good practice, since your models will be unusable outside of an HTTP request, like model factories or commands and even REPL. Just inject service within the controller and execute its methods before calling the create method on the model. |
Beta Was this translation helpful? Give feedback.
-
This is what I ended up doing. It works perfectly even though maybe it's not the most elegant solution: My List model (list_model.ts) - note the import { DateTime } from 'luxon'
import { BaseModel, column, hasMany } from '@adonisjs/lucid/orm'
import type { HasMany } from '@adonisjs/lucid/types/relations'
import ListProperty from '#models/list_property'
import { withWorkspace } from '#models/base_workspace_model'
import { withUser } from '#models/base_user_model'
import { compose } from '@adonisjs/core/helpers'
export default class List extends compose(BaseModel, withWorkspace(), withUser()) {
@hasMany(() => ListProperty)
declare properties: HasMany<typeof ListProperty>
@column({ isPrimary: true })
declare id: number
@column()
declare name: string
@column()
declare source: string
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime
} The workspace composable class (base_workspace_model.ts): import { belongsTo, column } from '@adonisjs/lucid/orm'
import Workspace from '#models/workspace'
import type { BelongsTo } from '@adonisjs/lucid/types/relations'
import { type BaseModel } from '@adonisjs/lucid/orm'
import type { NormalizeConstructor } from '@adonisjs/core/types/helpers'
import type { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model'
import WorkspaceService from '#services/workspace_service'
import app from '@adonisjs/core/services/app'
export function withWorkspace () {
return <Model extends NormalizeConstructor<typeof BaseModel>>(superclass: Model) => {
class BaseWorkspaceModel extends superclass {
@column()
declare workspaceId: number
@belongsTo(() => Workspace)
declare workspace: BelongsTo<typeof Workspace>
}
if (app.getEnvironment() !== 'console') {
BaseWorkspaceModel.before('create', async (entity: BaseWorkspaceModel) => {
const workspaceService = await app.container.make(WorkspaceService)
await workspaceService.assign(entity)
})
BaseWorkspaceModel.before('fetch', async (query: ModelQueryBuilderContract<typeof BaseWorkspaceModel>) => {
const workspaceService = await app.container.make(WorkspaceService)
workspaceService.filter(query)
})
BaseWorkspaceModel.before('find', async (query: ModelQueryBuilderContract<typeof BaseWorkspaceModel>) => {
const workspaceService = await app.container.make(WorkspaceService)
workspaceService.filter(query)
})
}
return BaseWorkspaceModel
}
}
interface BaseWorkspaceModel {
workspaceId: number
workspace: BelongsTo<typeof Workspace>
}
export type { BaseWorkspaceModel } My auth middleware (auth_middleware.ts): import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
import type { Authenticators } from '@adonisjs/auth/types'
import app from '@adonisjs/core/services/app'
import UserService from '#services/user_service'
import WorkspaceService from '#services/workspace_service'
/**
* Auth middleware is used authenticate HTTP requests and deny
* access to unauthenticated users.
*/
export default class AuthMiddleware {
/**
* The URL to redirect to, when authentication fails
*/
redirectTo = '/login'
async handle (ctx: HttpContext,
next: NextFn,
options: {
guards?: (keyof Authenticators)[]
} = {}) {
await ctx.auth.authenticateUsing(options.guards, { loginRoute: this.redirectTo })
app.container.singleton(UserService, () => new UserService(ctx.auth))
app.container.singleton(WorkspaceService, () => new WorkspaceService(ctx.auth))
return next()
}
} My workspace service (workspace_service.ts): import User from '#models/user'
import type { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model'
import type { BaseWorkspaceModel } from '#models/base_workspace_model'
import { BaseModel } from '@adonisjs/lucid/orm'
import { Authenticator } from '@adonisjs/auth'
import type { Authenticators } from '@adonisjs/auth/types'
export default class WorkspaceService {
constructor (protected auth: Authenticator<Authenticators>) {
}
getUser (): User | undefined {
return this.auth.user
}
async assign (model: BaseWorkspaceModel) {
const user = this.getUser()
if (user) {
await user.load('workspace')
model.workspaceId = user.workspaceId
}
}
filter (query: ModelQueryBuilderContract<typeof BaseModel>) {
const user = this.getUser()
if (user) {
query.andWhere('workspaceId', user.workspaceId)
}
}
} This allows me to add the |
Beta Was this translation helpful? Give feedback.
Yes this does not work as expected. Because you are using the
app.container
instance which is a global instance of the container and does not have any idea about the ongoing HTTP request.Creating model hooks that relies on HTTP request is not really a good practice, since your models will be unusable outside of an HTTP request, like model factories or commands and even REPL.
Just inject service within the controller and execute its methods before calling the create method on the model.