When a new user signs up, I want to create a number of relations to be automatically be set up as well. Currently, I am doing it this way:
const user = await User.create({
email,
password: hashedPassword,
}).save();
const settings = await Settings.create({
userId: user.id,
}).save();
const settings = await Profile.create({
userId: user.id,
}).save();
From what I've read, transactions are the best way to handle dependant operations, but PG doesn't create user ID until it is saved.
This is the best I could come up with transactions:
const user = await User.create({
email,
password: hashedPassword,
}).save();
const settings = await Settings.create({
userId: user.id,
});
const profile = await Profile.create({
userId: user.id,
});
await getConnection().transaction(async (transactionalEntityManager) => {
await transactionalEntityManager.save(settings);
await transactionalEntityManager.save(profile);
});
However, there is still the possibility that the user saves, but the relations do not.
Is there a better (or easier way) to handle this?
user.entity.ts
#Entity()
export class User {
#PrimaryGeneratedColumn()
id!: number;
#CreateDateColumn()
createdAt: Date;
#UpdateDateColumn()
updatedAt: Date;
#Column({ unique: true })
email!: string;
#Column()
password!: string;
#OneToOne(() => Settings, (settings) => settings.user)
settings: Settings;
#OneToOne(() => Profile, (profile) => profile.user)
profile: Profile;
}
settings.entity.ts
#Entity()
export class Settings {
#PrimaryColumn()
userId: number;
#OneToOne(() => User, (user) => user.settings)
user: User;
}
profile.entity.ts
#Entity()
export class Profile {
#PrimaryColumn()
userId: number;
#OneToOne(() => User, (user) => user.profile)
user: User;
}
The save method loads the relations before INSERT, therefore it is not possible to perform save for settings, The generated query will first try to SELECT the user and only then INSERT settings, as you said - inside a transaction it is not possible to select uncommitted records, and that why it doesn't work.
What can be done? Two options:
Save the entities nested
const user = await getConnection()
.transaction((transactionalEntityManager) => {
const userObj = User.create({
email,
password: hashedPassword,
})
userObj.profile = Profile.create({});
userObj.settings = Settings.create({});
return transactionalEntityManager.save(User, userObj);
});
Insert the user, and then insert the settings and profile
const userInsertResult = await getConnection()
.transaction(async (transactionalEntityManager) => {
const userInsertResult = await transactionalEntityManager
.createQueryBuilder(User,'user')
.insert()
.into(User)
.values({
email,
password: hashedPassword
}).execute();
const settingsInsertResult = await transactionalEntityManager
.createQueryBuilder(Settings,'settings')
.insert()
.into(Settings)
.values({
userId: userInsertResult.raw.id
}).execute();
const profileInsertResult = await transactionalEntityManager
.createQueryBuilder(Profile,'profile')
.insert()
.into(Profile)
.values({
userId: userInsertResult.raw.id
}).execute();
return userInsertResult
});
Related
On Creation of an account I need to make 2 collections 1 for Users 1 for Companies.
Within each one, I need to capture the UID. I just cant seem to grab it. I keep getting undefined when console.log it.
component
const handleSubmit = async (e) => {
e.preventDefault()
setError('')
try {
await createUser(email, password).then(() => {
if (false) throw Error('Error')
//NEED Get UserID
addDoc(collection(db, 'Companies'), {
name: company,
//owner: userID,
users: [],
assets: []
}).then((docRef) => {
let companyID = docRef.id
addDoc(collection(db, 'Users'), {
name: name,
email: email,
company: [company],
companyID: [companyID]
//UserID: UserID
})
})
})
authContext
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState({})
const createUser = (email, password) => {
return createUserWithEmailAndPassword(auth, email, password)
}
I have tried everything in the firebase documentation but I believe since I am using context it may process data a bit differently.
The createUser() function is returning a UserCredential object. You can read the user's UID from it. Try refactoring the code as shown below:
const handleSubmit = async (e) => {
e.preventDefault()
setError('')
const { user } = await createUser(email, password)
const docRef = await addDoc(collection(db, 'Companies'), {
name: company,
owner: user.uid,
users: [],
assets: []
})
await addDoc(collection(db, 'Users'), {
name: name,
email: email,
company: [company],
companyID: [docRef.id]
UserID: user.uid
})
}
I'm having problems using TypeOrm hook "BeforeUpdate"
I'm trying to update the user entity password, by passing a simple string and calling the save method to trigger the beforeUpdate hook and then hash the password, but this hook is not working while calling the save method.
This is what i have
user.service.ts
async update(id: number, updateUserDto: UpdateUserDto) {
const roles =
updateUserDto.roles &&
(await Promise.all(
updateUserDto.roles.map((name) => this.preloadRoleByName(name))
));
const user = await this.userRepository.findOneOrFail(id);
if (!user) {
throw new NotFoundException(`User with ID #${id} not found`);
}
const toSaveUser = {
...user,
...updateUserDto,
roles,
};
return await this.userRepository.save(toSaveUser);
}
user.entity.ts
.
.
.
#Column()
#Exclude()
password: string;
#BeforeInsert()
#BeforeUpdate()
private async hashPassword() {
const rounds = 10;
const salt = await bcrypt.genSalt(rounds);
this.password = await bcrypt.hash(this.password, salt);
}
user.controller.ts
#Patch(":id")
#UseInterceptors(ClassSerializerInterceptor)
async update(#Param("id") id: string, #Body() updateUserDto: UpdateUserDto) {
return await this.usersService.update(+id, updateUserDto);
}
What I'm doing wrong?
BeforeInsert hook works or if I call userRepository.preload() method to update it works but it doesn't replace the relationship of the role, that's why I take this approach.
Any ideas?
Something to be aware of when using the triggers is that they might require instances of the entity to run.
Problem:
const user = await this.userRepository.findOneOrFail(id); // entity instance
const toSaveUser = { ...user, ...updateUserDto, roles }; // plain object
return await this.userRepository.save(toSaveUser); // not running trigger
Solution:
const user = await this.userRepository.findOneOrFail(id); // entity instance
// still entity instance
const toSaveUser = this.userRepository.create({
...user,
...updateUserDto,
roles,
});
return await this.userRepository.save(toSaveUser); // running trigger
And you should be good.
Since you are using repository.save() with an arbitrary object in your code, the trigger does not run. If we instead use repository.create() to create the instance, the trigger will now run.
The reason why your solution worked with repository.preload() is because it returns an instance. Repository api examples
You need to create DTO(Data Transfer Object) first and then update. this code is for update temporary password.
How create DTO:
this.<YOUR_REPOSITORY_NAME>.create(<INPUT_OBJECT>)
Example:
async updatePassword(id: number, tempPassword: string): Promise<boolean> {
let newUser = { tempPassword: tempPassword };
const userDto = this.userAccountRepository.create(newUser)
const userAccount = this.userAccountRepository.update(
{
userAccountId: id,
},
userDto
);
if (userAccount) {
return true;
} else {
return false;
}
}
This is my entity:
import {
BeforeInsert,
BeforeUpdate,
Column,
Entity,
JoinColumn,
OneToMany,
OneToOne,
PrimaryColumn,
} from 'typeorm';
import { Users } from '../users/users.entity';
const crypto = require('crypto');
#Entity()
export class UserAccount {
#PrimaryColumn()
userAccountId: number;
#OneToOne(() => Users, { cascade: true })
#JoinColumn({ name: 'userAccountId' })
#Column({ nullable: true })
tempPassword: string;
#BeforeInsert()
#BeforeUpdate()
async hashPassword(): Promise<void> {
if (!!this.password) {
this.password = crypto.createHmac('sha256', this.password).digest('hex');
}
if (!!this.tempPassword) {
this.tempPassword = crypto
.createHmac('sha256', this.tempPassword)
.digest('hex');
}
}
}
Two inputs from my side,
Separate function for encrypting updated password -
#BeforeUpdate()
async hashPasswordBeforeUpdate() {
this.password = await bcrypt.hash(this.password, 10);
}
Try creating PUT request instead of a PATCH request
I am able to generate a valid update query, sharing just for reference-
query: UPDATE `users` SET `levelId` = ?, `updatedAt` = ?, `password` = ? WHERE `id` IN (?) -- PARAMETERS: [null,"2021-05-07T07:27:47.198Z","$2b$10$uQOMNv57BZLB/W/9SWPbke6/OMdIDWxv3i25A8rUhA0/vEMloWb2W",1]
I am using in my NestJs application type-orm.
I have 2 entities:
User
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column({ nullable: true })
gender: string;
#OneToMany(() => Address, (address) => address.user)
address: Address;
}
Address
#Entity()
export class Address {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(() => User, (user) => user.address)
user: User;
}
The idea is next: Each user can have multiple addresses, this is why i used OneToMany.
My provider looks like this:
async add(): Promise < User > {
try {
const newPost = this.usersRepository.create({
gender: 'm',
});
return await this.usersRepository.save(newPost);
} catch (err) {
return err;
}
}
It works and in db, in the table user i can add gender: 'm'.
Issue: How to add in add() function and the addresses, because now i can add just the user. Question: How to change the add() function to be able to add and addresses?
First of all, you have to fix the User entity by making addresses as an Array of addresses,
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column({ nullable: true })
gender: string;
#OneToMany(() => Address, (address) => address.user)
address: Address[];
}
For your question, you can achieve adding the address by saving them first then bind them with the user:
async add(): Promise < User > {
try {
let adresses:Array<Address> = [] ; array of type Address entity
for(let adress of sourceOfyouAdressses) //sourceOfyouAdressses = the array of addresses you want add
{
let adrs = await this.adress Repository.save({city: adress.city }); // here you save you adress informations
adresses.push(adrs );
}
const newPost = this.usersRepository.create({
gender: 'm',
address:adresses
});
return await this.usersRepository.save(newPost);
} catch (err) {
return err;
}
}
I have two functions that trigger onCreate and onUpdate however, the {uid} in onUpdate is returning undefined, whilst onCreate returns the {uid}.
How can I get the {uid} to work for onUpdate?
onUpdate.f.js - {uid} is undefined
exports = module.exports = functions.firestore
.document('users/{uid}/alerts/{name}') //UID is the User ID value stored in alerts
.onUpdate(snap => {
const user = snap.data();
console.log(user);
const msg = {
to: user.email,
from: 'notifications#example.com',
templateId: user.template,
dynamic_template_data: {
firstName: user.firstName,
email: user.email,
id: user.uid
}
};
return sgMail.send(msg).catch(err => console.log(`${user.email} - ${err}`));
});
onCreate.f.js - {uid} is correct
exports = module.exports = functions.firestore
.document('users/{uid}/alerts/{name}')
.onCreate(snap => {
const user = snap.data();
console.log(user);
const msg = {
to: user.email,
from: 'notifications#example.com',
templateId: user.template,
dynamic_template_data: {
firstName: user.firstName,
email: user.email,
id: user.uid
}
};
return sgMail.send(msg).catch(err => console.log(`${user.email} - ${err}`));
});
Fields in doc Alerts from frontend
doCreateAlert = (id, email, firstName, lastName, alertType, transactionEmailId) => {
const db = this.firestore;
return db.doc(`users/${id}/alerts/${alertType}`).set({
uid: id,
name: alertType,
email: email,
firstName: firstName,
lastName: lastName,
template: transactionEmailId,
dateCreated: new Date(),
dateModified: new Date()
});
};
The onUpdate is triggered by updating the database with onClick={this.updateAlert} as
updateAlert = () => {
const { firebase, userID } = this.props;
const companyTypeSetup = db.doc(`users/${userID}/alerts/emailVerified`);
companyTypeSetup.update({
dateModified: new Date()
});
};
on the frontend I receive the error of
Uncaught (in promise) Error: No document to update: projects/app/databases/(default)/documents/users/undefined/alerts/emailVerified
and the function is never run. If I manually update the doc in Firestore, I get an error in the firebase functions log as
TypeError: snap.data is not a function
at module.exports.functions.firestore.document.onUpdate.snap (/user_code/lib/auth/onUpdate.f.js:17:23)
at cloudFunctionNewSignature (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:105:23)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:135:20)
at /var/tmp/worker/worker.js:754:24
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
As the guide shows, onUpdate has two parameters: change and context. You use change since you may want to access the value before the update or after the update. Assuming you want the value after the update, that would look like this:
exports = module.exports = functions.firestore
.document('users/{uid}/alerts/{name}') //UID is the User ID value stored in alerts
.onUpdate((change, context) => {
const user = change.after.data();
console.log(user);
const msg = {
to: user.email,
from: 'notifications#example.com',
templateId: user.template,
dynamic_template_data: {
firstName: user.firstName,
email: user.email,
id: user.uid
}
};
return sgMail.send(msg).catch(err => console.log(`${user.email} - ${err}`));
});
Problem can easily be solved by reading the documents at Handle Event Data. However, if you are like me and skim the documents then the solution is
.onUpdate(change => {
const user = change.after.data();
I'm trying to use the model.create() shorthand to create a new Company with an existing User ObjectId.
const userSchema = new mongoose.Schema({
firstName: String,
lastName: String
});
const companySchema = new mongoose.Schema({
companyName: String,
users: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
});
const User = mongoose.model('user2', userSchema);
const Company = mongoose.model('company2', userSchema);
The following user already exists in the users collection
{"_id":"5a9633031fe445041c07afd3","firstName":"John","lastName":"Doe","__v":0}
Now I want to create a new company with this existing user:
Company.create({
companyName: 'Foo, Inc.',
users: {5a9633031fe445041c07afd3}
}).then(function(err, company) {
console.log(err, company);
});
I've tried variations of the user as:
users: '5a9633031fe445041c07afd3'
users: [5a9633031fe445041c07afd3]
users: [{5a9633031fe445041c07afd3}]
How can I save this document?
try this
let model = new Company();
const body = {
companyName: 'Foo, Inc.',
users: "5a9633031fe445041c07afd3"
};
model = Object.assign(model, body);
model.then((company) => {
console.log(company);
}).catch((err) => {
console.log(err);
})