Skip to content

Commit

Permalink
fix: Fix TypeORM helper one to many and many to many should be readon…
Browse files Browse the repository at this point in the history
…ly and writeonly (#953)
  • Loading branch information
ktutnik committed Jun 3, 2021
1 parent 2b5429e commit b66ea12
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 24 deletions.
36 changes: 16 additions & 20 deletions packages/typeorm/src/generic-controller.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { api, authorize, Class, entity, GenericControllers, KeyOf, NestedRepository, Repository } from "@plumier/core"
import { authorize, Class, entity, GenericControllers, Repository } from "@plumier/core"
import {
ControllerBuilder,
genericControllerRegistry,
RepoBaseControllerGeneric,
RepoBaseNestedControllerGeneric,
GenericControllerConfiguration,
createGenericController,
EntityWithRelation,
NestedRepositoryFactory
GenericControllerConfiguration,
NestedRepositoryFactory,
RepoBaseControllerGeneric,
RepoBaseNestedControllerGeneric,
} from "@plumier/generic-controller"
import reflect, { generic, noop, useCache } from "@plumier/reflect"
import { parse } from "acorn"
Expand Down Expand Up @@ -35,19 +33,18 @@ function normalizeEntityNoCache(type: Class) {
const relations = storage.filterRelations(type)
for (const col of relations) {
const rawType: Class = (col as any).type()
const type = col.relationType === "one-to-many" || col.relationType === "many-to-many" ? [rawType] : rawType
Reflect.decorate([reflect.type(x => type)], (col.target as Function).prototype, col.propertyName, void 0)
if (col.relationType === "many-to-one") {
// TODO
Reflect.decorate([entity.relation()], (col.target as Function).prototype, col.propertyName, void 0)
if (col.relationType === "many-to-many" || col.relationType === "one-to-many") {
const inverseProperty = inverseSideParser(col.inverseSideProperty as any)
const decorators = [
reflect.type(x => [rawType]),
entity.relation({ inverseProperty }),
authorize.readonly(),
authorize.writeonly()
]
Reflect.decorate(decorators, (col.target as Function).prototype, col.propertyName, void 0)
}
else {
const inverseProperty = inverseSideParser(col.inverseSideProperty)
const cache = genericControllerRegistry.get(rawType)
// if entity handled with generic controller then hide all one to many relation
if (cache)
Reflect.decorate([api.readonly(), api.writeonly()], (col.target as Function).prototype, col.propertyName, void 0)
Reflect.decorate([entity.relation({ inverseProperty })], (col.target as Function).prototype, col.propertyName, void 0)
Reflect.decorate([reflect.type(x => rawType), entity.relation()], (col.target as Function).prototype, col.propertyName, void 0)
}
}
}
Expand All @@ -60,8 +57,7 @@ const normalizeEntity = useCache(normalizeEntityCache, normalizeEntityNoCache, x
// ---------------------- INVERSE PROPERTY PARSER ---------------------- //
// --------------------------------------------------------------------- //

function inverseSideParser(expr: string | ((t: any) => any) | undefined) {
if (!expr || typeof expr === "string") return expr
function inverseSideParser(expr: ((t: any) => any)) {
const node = parse(expr.toString(), { ecmaVersion: 2020 })
return getMemberExpression(node)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2039,7 +2039,9 @@ Object {
"items": Object {
"$ref": "#/components/schemas/Animal",
},
"readOnly": true,
"type": "array",
"writeOnly": true,
},
"email": Object {
"type": "string",
Expand Down
90 changes: 86 additions & 4 deletions tests/behavior/typeorm/typeorm-generic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ describe("Filter", () => {
beforeAll(async () => {
app = await createApp()
const parentRepo = new TypeORMRepository(Parent)
const repo = new TypeORMNestedRepository<Parent,Child>([Parent, "children"])
const repo = new TypeORMNestedRepository<Parent, Child>([Parent, "children"])
await parentRepo.nativeRepository.delete({})
await repo.nativeRepository.delete({})
parent = await parentRepo.insert(<Parent>{ string: "lorem", number: 1, boolean: true })
Expand Down Expand Up @@ -377,7 +377,7 @@ describe("CRUD", () => {
const UserController = MyGenericController(User)
const UserAnimalController = MyGenericController([User, "animals"])
const mock = console.mock()
await createApp([UserController, UserAnimalController, User, Animal], {mode: "debug" })
await createApp([UserController, UserAnimalController, User, Animal], { mode: "debug" })
console.mockClear()
expect(mock.mock.calls).toMatchSnapshot()
})
Expand Down Expand Up @@ -1978,6 +1978,88 @@ describe("CRUD", () => {
expect(cleanupConsole(mock.mock.calls)).toMatchSnapshot()
console.mockClear()
})
it("Should not able to populate array relation from parent", async () => {
function createApp(entities: Function[], opt?: Partial<Configuration>) {
return new Plumier()
.set(new WebApiFacility())
.set(new TypeORMFacility({ connection: getConn(entities) }))
.set(new JwtAuthFacility({ secret: "secret", globalAuthorize: "Public" }))
.set({ ...opt, controller: entities as any })
.initialize()
}
@Entity()
@genericController()
class User {
@PrimaryGeneratedColumn()
id: number
@Column()
email: string
@Column()
name: string
@OneToMany(x => Animal, x => x.user)
@genericController()
animals: Animal[]
}
@Entity()
@genericController()
class Animal {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@ManyToOne(x => User, x => x.animals)
user: User
}
const app = await createApp([User, Animal], { mode: "production" })
const user = await createUser(User)
const animalRepo = getManager().getRepository(Animal)
await Promise.all(Array(50).fill(1).map((x, i) => animalRepo.insert({ name: `Mimi ${i}`, user })))
await supertest(app.callback())
.patch(`/users/${user.id}`)
.send({ animals: [] })
.expect(403)
})
it("Should not able to retrieve array relation from parent", async () => {
function createApp(entities: Function[], opt?: Partial<Configuration>) {
return new Plumier()
.set(new WebApiFacility())
.set(new TypeORMFacility({ connection: getConn(entities) }))
.set(new JwtAuthFacility({ secret: "secret", globalAuthorize: "Public" }))
.set({ ...opt, controller: entities as any })
.initialize()
}
@Entity()
@genericController()
class User {
@PrimaryGeneratedColumn()
id: number
@Column()
email: string
@Column()
name: string
@OneToMany(x => Animal, x => x.user)
@genericController()
animals: Animal[]
}
@Entity()
@genericController()
class Animal {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@ManyToOne(x => User, x => x.animals)
user: User
}
const app = await createApp([User, Animal], { mode: "production" })
const user = await createUser(User)
const animalRepo = getManager().getRepository(Animal)
await Promise.all(Array(50).fill(1).map((x, i) => animalRepo.insert({ name: `Mimi ${i}`, user })))
await supertest(app.callback())
.patch(`/users/${user.id}?select=animals`)
.send({ animals: [] })
.expect(403)
})
})
describe("Nested CRUD Many To One", () => {
async function createUser<T>(type: Class<T>): Promise<T> {
Expand Down Expand Up @@ -2724,7 +2806,7 @@ describe("Repository", () => {
normalizeEntity(User)
normalizeEntity(Animal)
const userRepo = new TypeORMRepository(User)
const animalRepo = new TypeORMNestedRepository<User,Animal>([User, "animals"])
const animalRepo = new TypeORMNestedRepository<User, Animal>([User, "animals"])
const email = `${random()}@gmail.com`
const user = await userRepo.insert({ name: "John Doe" })
await Promise.all([
Expand Down Expand Up @@ -2760,7 +2842,7 @@ describe("Repository", () => {
normalizeEntity(User)
normalizeEntity(Animal)
const userRepo = new TypeORMRepository(User)
const animalRepo = new TypeORMNestedRepository<User,Animal>([Animal, "user"])
const animalRepo = new TypeORMNestedRepository<User, Animal>([Animal, "user"])
const user = await userRepo.insert({ name: "John Doe" })
await Promise.all([
animalRepo.insert(user.id, { name: "Mimi" }),
Expand Down

0 comments on commit b66ea12

Please sign in to comment.