MikroORM Can't map join-table that uses a composite primary-key that includes a composite foreign-key - mikro-orm

I get an error when mapping a join table that has a composite primary-key that includes a foreign-key to a composite primary-key. I believe the problem is with UserRole.user.
Stack trace
TypeError: Cannot read property 'id' of undefined
at /Users/emurphy/projects/aperture/aap-api/node_modules/#mikro-orm/core/utils/Utils.js:369:29
at Array.map (<anonymous>)
at Function.getOrderedPrimaryKeys (/Users/emurphy/projects/aperture/aap-api/node_modules/#mikro-orm/core/utils/Utils.js:367:33)
at /Users/emurphy/projects/aperture/aap-api/node_modules/#mikro-orm/core/utils/Utils.js:371:37
at Array.map (<anonymous>)
at Function.getOrderedPrimaryKeys (/Users/emurphy/projects/aperture/aap-api/node_modules/#mikro-orm/core/utils/Utils.js:367:33)
at /Users/emurphy/projects/aperture/aap-api/node_modules/#mikro-orm/core/utils/Utils.js:371:37
at Array.map (<anonymous>)
at Function.getOrderedPrimaryKeys (/Users/emurphy/projects/aperture/aap-api/node_modules/#mikro-orm/core/utils/Utils.js:367:33)
at EntityFactory.findEntity (/Users/emurphy/projects/aperture/aap-api/node_modules/#mikro-orm/core/entity/EntityFactory.js:95:35)
To Reproduce
Create the following entities:
#Entity()
export class Organization {
#PrimaryKey({ type: 'uuid', defaultRaw: 'uuid_generate_v4()' })
id: string = v4();
#Unique()
#Property({ columnType: 'varchar' })
name: string;
#OneToMany({ entity: () => User, mappedBy: (user) => user.organization, cascade: [] })
users = new Collection<User>(this);
constructor(value: Partial<Organization> = {}) {
Object.assign(this, value);
this.users = this.users || new Collection<User>(this);
}
}
#Entity()
export class User {
#PrimaryKey({ columnType: 'varchar' })
id: string;
#Index()
#ManyToOne({
entity: () => Organization,
primary: true,
wrappedReference: true,
index: true,
cascade: [],
onDelete: 'no action',
})
organization: IdentifiedReference<Organization>;
#Property({ columnType: 'varchar' })
firstName: string;
#Property({ columnType: 'varchar' })
lastName: string;
#Property({ columnType: 'varchar' })
email: string;
#OneToMany({ entity: () => UserRole, mappedBy: (userRole) => userRole.user })
userRoles = new Collection<UserRole>(this);
[PrimaryKeyType]: [string, string];
constructor(value: Partial<User> = {}) {
Object.assign(this, value);
this.userRoles = this.userRoles || new Collection<UserRole>(this);
}
}
#Entity()
export class Role {
#PrimaryKey({ columnType: 'varchar' })
id: string;
#Property({ columnType: 'varchar' })
name: string;
#OneToMany({ entity: () => UserRole, mappedBy: (userRole) => userRole.role })
userRoles = new Collection<UserRole>(this);
constructor(value: Partial<Role> = {}) {
Object.assign(this, value);
this.userRoles = this.userRoles || new Collection<UserRole>(this);
}
}
#Entity()
export class UserRole {
#ManyToOne({
entity: () => User,
inversedBy: (x) => x.userRoles,
primary: true,
wrappedReference: true,
cascade: [],
onDelete: 'cascade',
})
user: Reference<User>;
#ManyToOne({
entity: () => Role,
inversedBy: (x) => x.userRoles,
primary: true,
wrappedReference: true,
cascade: [],
onDelete: 'no action',
})
role: IdentifiedReference<Role>;
[PrimaryKeyType]: [string, string, string];
constructor(value: Partial<UserRole> = {}) {
Object.assign(this, value);
}
}
Run either of the following queries:
// query and map just the join table
await this.em.find(UserRole, { user: { $eq: [userId, orgId] } })
// or try to populate it
await this.em.findOne(
User,
{ id: { $eq: userId }, organization: { $eq: orgId } },
{ populate: { userRoles: LoadStrategy.JOINED } },
);
Versions
Dependency
Version
node
14.15.3
typescript
4.1.5
mikro-orm
4.4.4
mikro-orm/postgresql
4.4.4
I have tried removing the UserRole.role property and the error still occurs. I have tried querying the table directly and populating it as part of finding another entity (User). I have tried using the query builder. I have tried using normal entities instead of wrapped entities. I have tried using em.map(...) on the results of a raw execute.
Am I setting it up wrong or is this just a bug in the framework? I couldn't find examples for this specific scenario.
Update
The issue was fixed here: https://github.com/mikro-orm/mikro-orm/issues/1624

Related

How to remain same current page pagination in redux rtk

I build an applicant with Redux RTK with createEntity
Two issue that I couldn't found it on the docs
CreateEntity is only return {ids: [], entities: []}? Is possible that return eg: totalPage from the response also?
Cache page only work on the hardcode initialState in createSlice if the pageQuery is same.
First question:
Getting the response from server was
{
users: [{id: 1}, ...]
totalPage: 100
}
I'd like to send totalPage to auto generated hook also.
export const usersAdapter = createEntityAdapter({})
export const initialState = usersAdapter.getInitialState()
export const usersApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
getUsers: builder.query({
query: (args) => {
return {
url: '/api/users',
method: 'GET',
params: { page: 1, limit: 10 }
}
},
validateStatus: (response, result) => {
return response.status === 200 && !result.isError
},
transformResponse: (responseData) => {
const loadedUsers = responseData?.users.map((user) => user)
console.log("responseData: ", responseData) // <----- return { users: [], totalPage: 100 }. Could we set this totalPage value into Adapter?
return usersAdapter.setAll(initialState, loadedUsers)
},
providesTags: (result, error, arg) => {
if (result?.ids) {
return [
{ type: "User", id: "LIST" },
...result.ids.map((id) => ({ type: "User", id })),
]
} else return [{ type: "User", id: "LIST" }]
},
})
})
})
Use the hook in component
const { data } = useGetUsersQuery("page=1&limit=10");
console.log(data) // { ids: [], entity: [{}, {}] };
// expected return { ids: [], entity: [{}, {}], totalPage: 100}
Second question:
Store the page query in createSlice. The edit page will be remain same after refresh if the page query value same as initialState value.
import { createSlice } from "#reduxjs/toolkit"
const userReducer = createSlice({
name: "user",
initialState: {
query: `page=1&limit=10`,
},
reducers: {
setUserPageQuery: (state, action) => {
const query = action.payload
state.query = query
},
},
})
Page url Flow:
localhost:3000/users > localhost:3000/users/4 > refresh -> data will remain after refresh browser. (query "page=1&limit10" same as createSlice initialState value )
localhost:3000/users > localhost:3000/users/15 > refresh -> data state will gone after refresh browser. (query "page=2&limit10" different from createSlice initialState value )
Appreciate all the reply :)

Mikro ORM : What is the best way to upsert a collection

In the following scenario a whole SalesOrder entity is passed in to the server
items collection of sales order entity can have new items, removed items and updated items.
just persisting the SalesOrder don't work (wont remove or update)
this is the way i got it to work. is there a better way? or an I missing something?
async save(#Body() item: SalesOrder) {
let result: SalesOrder;
result = await em.getRepository<SalesOrder>(SalesOrder).findOne(item.id);
item.items.forEach(k => k.salesOrder = item.id)
await this.em.getRepository(SalesOrderItem).nativeDelete({ salesOrder: item.id })
for (const [i, it] of item.items.entries()) {
const x = em.getRepository(SalesOrderItem).create({ ...it })
await em.persistAndFlush(x)
}
wrap(result).assign({ ...item, items: null }, {})
await em.getRepository(SalesOrder).persistAndFlush(result);
await em.flush()
}
#Entity()
export class SalesOrder extends BaseEntity {
#PrimaryKey( )
id: number;
#OneToMany(i => SalesOrderItem, t => t.salesOrder, { eager: true, cascade: [Cascade.ALL] })
items = new Collection<SalesOrderItem>(this);
}
#Entity()
export class SalesOrderItem extends BaseEntity {
#ManyToOne({ entity: () => InventoryItem, eager: true, primary: true })
item: IdentifiedReference<InventoryItem>;
#Property({ type: JsonType, columnType: 'jsonb', nullable: true })
configuration?: SalesOrderItem[]
#ManyToOne({ entity: () => SalesOrder, hidden: true, primary: true })
salesOrder: IdentifiedReference<SalesOrder>;
#Property({ nullable: false })
qty: number;
#Property({ nullable: false })
unitPrice: number;
[PrimaryKeyType]: [string, string]
}

loopback 4 how to avoid creating more than 5 items in a shopping cart

I am new to loopback 4, and almost all the documentation I found is for lower versions. I have a shopping cart, but I need to avoid it to have more than 5 items... how can I made this constraint?
These are my models:
import {Entity, hasMany, model, property} from '#loopback/repository';
import {Item} from './item.model';
#model()
export class Shoppingcar extends Entity {
#property({
type: 'string',
id: true,
generated: true,
})
id?: string;
#property({
type: 'string',
required: true,
})
desc: string;
#property({
type: 'string',
required: true,
}
})
shopper: string;
#hasMany(() => Item)
items: Item[];
constructor(data?: Partial<Gateway>) {
super(data);
}
}
export interface ShoppingcarRelations {
// describe navigational properties here
}
export type ShoppingcarWithRelations = Shoppingcar & ShoppingcarRelations;
and
import {Entity, model, property, belongsTo} from '#loopback/repository';
import {Shoppingcar} from './shoppingcar.model';
#model()
export class Item extends Entity {
#property({
type: 'string',
id: true,
generated: true,
})
id?: string;
#property({
type: 'string',
required: true,
})
name: string;
#property({
type: 'string',
required: true,
})
date: string;
#belongsTo(() => Shoppingcar)
shoppingcarId: string;
constructor(data?: Partial<Item>) {
super(data);
}
}
export interface ItemRelations {
// describe navigational properties here
}
export type ItemWithRelations = Item & ItemRelations;
Create one interceptor,
check for the item count in the interceptor, if the count is greater the 5 throw an error else continue with next()

Data does not gets added to database after adding email and password in authorization in firebase through vue.js

Data does not get added to database after adding email and password in authorization in Firebase through vue.js.
After:
firebase.auth().createUserWithEmailAndPassword(this.email, this.password)
This doesn't get executed:
ref.set({
alias: this.alias,
geolocation: null,
user_id: cred.user.uid
})
Email and Password gets added to Authorization. But other fields like alias, geolocation, user_id does not get added to the database.
import slugify from 'slugify'
import db from '#/firebase/init'
import firebase from 'firebase'
export default {
name: 'Signup',
data(){
return {
email: null,
password: null,
alias: null,
feedback: null,
slug: null,
}
},
methods: {
signup() {
if(this.alias && this.email && this.password) {
this.feedback = null
this.slug = slugify(this.alias, {
replacement: '-',
remove: /[$*_+~.()'"!\-:#]/g,
lower: true
})
let ref = db.collection('users').doc(this.slug)
ref.get().then(doc => {
if(doc.exists){
this.feedback = 'This alias already exists'
}
else {
firebase.auth().createUserWithEmailAndPassword(this.email, this.password)
.then(cred => {
ref.set({
alias: this.alias,
geolocation: null,
user_id: cred.user.uid
})
}).then(() => {
this.$router.push({ name: 'GMap' })
})
.catch(err => {
console.log(err)
this.feedback = err.message
})
}
})
} else {
this.feedback = 'You must enter all fields'
}
}
}
}
One solution could be to use cloud functions to write to the database on userCreate
https://firebase.google.com/docs/functions/auth-events

Use action creator to dispatch action in another action creator

I'm wondering if there is a pattern that allows you to use action creators inside of other action creators. The modifyMassProperty action creator lets you pass any number of actions which are then iterated over and dispatched accordingly. I would very much like to be able to use this method in the getOrbitalBurn action creator since it would be semantically more appealing than using the dispatch method made available by the thunk three times in a row. I'm confident I must either have missed something, or that I'm guilty of getting tangled up in some sort of anti pattern that I concocted during one of my lesser days.
export const modifyMassProperty = (
...massProperties: MassProperty[]
): ThunkAction<void, AppState, void, Action> => (
dispatch: Dispatch<ScenarioActionTypes>
) =>
massProperties.forEach(massProperty =>
dispatch({
type: MODIFY_MASS_PROPERTY,
payload: massProperty
})
);
export const getOrbitalBurn = (
payload: { primary: string; periapsis: number; apoapsis: number },
applyBurn = true
): ThunkAction<void, AppState, void, Action> => (
dispatch: Dispatch<ScenarioActionTypes>,
getState: any
) => {
const scenario = getState().scenario;
const primary = getObjFromArrByKeyValuePair(
scenario.masses,
'name',
payload.primary
);
const orbit = orbitalInsertion(primary, payload, scenario.g);
if (applyBurn) {
const [spacecraft] = scenario.masses;
dispatch({
type: MODIFY_MASS_PROPERTY,
payload: {
name: spacecraft.name,
key: 'vx',
value: orbit.x
}
});
dispatch({
type: MODIFY_MASS_PROPERTY,
payload: {
name: spacecraft.name,
key: 'vy',
value: orbit.y
}
});
dispatch({
type: MODIFY_MASS_PROPERTY,
payload: {
name: spacecraft.name,
key: 'vz',
value: orbit.z
}
});
}
dispatch({
type: MODIFY_SCENARIO_PROPERTY,
payload: {
key: 'orbitalInsertionV',
value: { x: orbit.x, y: orbit.y, z: orbit.z }
}
});
};

Resources