I have the following schema
export type MapDocument = Map & Document
#Schema({
timestamps: true,
versionKey: false,
id: true
})
export class Map {
constructor(partial?: Partial<Map>) {
if (partial)
Object.assign(this, partial)
}
#IsOptional()
#IsUUID()
#Prop({ type: Object, default: uuidv4, required: false})
#Exclude({ toPlainOnly: true })
_id?: Object
#ApiPropertyOptional({ type: String, format: 'uuid' })
#IsOptional()
#IsUUID()
id?: string
#ApiProperty()
#IsAscii()
#MaxLength(10)
#Prop()
building: string
#ApiProperty()
#IsInt()
#Prop()
#Transform(({ value }) => +value, { toClassOnly: true })
floor: number
#ApiPropertyOptional({ type: Image, format: 'uuid'})
#IsOptional()
#IsUUID()
#Prop({ type: String, ref: 'Image' })
#Transform(({ value }) => new Image(value).id, { toClassOnly: true })
image?: string
#ApiProperty({ type: [Marker] })
#IsArray()
#Type(() => Marker)
#Prop({ type: [MarkerSchema] })
markers?: Marker[]
}
const MapSchema = SchemaFactory.createForClass(Map)
MapSchema.index({ building: 1, floor: 1 }, { unique: true });
const mongooseLeanVirtuals = require('mongoose-lean-virtuals')
MapSchema.plugin(mongooseLeanVirtuals);
export { MapSchema }
export class UpdateMap extends PartialType(Map) {}
Marker is declared as follows
export type MarkerDocument = Marker & Document
#Schema({
timestamps: true,
versionKey: false,
id: true
})
export class Marker {
constructor(partial?: Partial<Marker>) {
if (partial)
Object.assign(this, partial)
}
#IsOptional()
#IsUUID()
#Prop({ type: Object, default: uuidv4, required: false})
#Exclude({ toPlainOnly: true })
_id?: Object
#ApiPropertyOptional({ type: String, format: 'uuid' })
#IsOptional()
#IsUUID()
id?: string
#ApiPropertyOptional({ type: Desk, format: 'uuid'})
#IsOptional()
#IsUUID()
#Prop({ type: String, required: false, ref: 'Desk' })
#Transform(({ value }) => new Desk(value).id, { toClassOnly: true })
desk?: string
#ApiProperty()
#IsNumberString()
#Prop()
xPercent: string
#ApiProperty()
#IsNumberString()
#Prop()
yPercent: string
}
const MarkerSchema = SchemaFactory.createForClass(Marker)
const mongooseLeanVirtuals = require('mongoose-lean-virtuals')
MarkerSchema.plugin(mongooseLeanVirtuals);
export { MarkerSchema }
export class UpdateMarker extends PartialType(Marker) {}
Important to note that has a field (desk) referencing another collection but I don't want these items to be stored in their own collection but as a subdocument of the 'maps' collection directly
The 'desk' schema is declared as follows
export type DeskDocument = Desk & Document
#Schema({
timestamps: true,
versionKey: false,
id: true
})
#ApiExtraModels(Image)
export class Desk {
constructor(partial?: Partial<Desk>) {
if (partial)
Object.assign(this, partial)
}
#IsOptional()
#IsUUID()
#Prop({ type: Object, default: uuidv4, required: false})
#Exclude({ toPlainOnly: true })
_id?: Object
#ApiPropertyOptional({ type: String, format: 'uuid' })
#IsOptional()
#IsUUID()
id?: string
#ApiProperty({ type: Board, format: 'uuid'})
#IsUUID('all')
#Prop({ type: String, required: true, ref: 'Board' })
#Transform(({ value }) => value.id, { toClassOnly: true })
board: string
#ApiProperty({ type: [Number] })
#IsArray()
#Prop({ type: () => [Number], required: true })
relays?: number[]
#ApiProperty()
#IsAscii()
#MaxLength(250)
#Prop()
name: string
#ApiProperty()
#IsAscii()
#MaxLength(250)
#Prop()
description: string
#ApiProperty()
#IsAscii()
#MaxLength(10)
#Prop()
building: string
#ApiProperty()
#IsInt()
#Prop()
#Transform(({ value }) => +value, { toClassOnly: true })
floor: number
#ApiProperty()
#IsAscii()
#MaxLength(10)
#Prop()
code: string
#ApiPropertyOptional({ type: [Image], format: 'uuid'})
#IsOptional()
#IsUUID('all', { each: true })
#Prop({ type: [String], required: false, ref: 'Image' })
#Transform(({ value }) => value.id, { toClassOnly: true })
images?: String[]
}
const DeskSchema = SchemaFactory.createForClass(Desk)
DeskSchema.index({ board: 1, relays: 1 }, { unique: true });
const mongooseLeanVirtuals = require('mongoose-lean-virtuals')
DeskSchema.plugin(mongooseLeanVirtuals)
export { DeskSchema }
export class UpdateDesk extends PartialType(Desk) {}
The find methods tried to populate everything down to desk (no need for me to populate deeper than that)
async findAll(): Promise<Map[]> {
return (await this.mapModel.find().populate({
path: 'image',
model: Image,
transform: (doc: Image) => new Image(doc),
}).populate({
path: 'markers',
model: Marker,
transform: (doc: Marker) => new Marker(doc),
populate: {
path: 'desk',
model: Desk,
transform: (doc: Desk) => new Desk(doc),
options: { lean: true, virtuals: true },
}
}).lean({ virtuals: true }).exec())
.map(map => new Map(map))
}
Two issues
Minor: If I don't specify lean: true as an option for the desk populate I get the full mongo document. This is not the case for the 'markers' array which relies on the 'lean' settings
Main: the desk object gets populated but the virtual 'id' field doesn't
This is the output I get:
{
"building": "A",
"floor": 1,
"image": {
"name": "OfficePlan12",
"url": "https://drive.google.com/uc?id=1H2nnIRjR2e7Z7yoVnHxLCTaYs8s5iHrT",
"updatedAt": "2022-06-24T09:03:03.786Z",
"id": "2b31f419-e069-4058-813e-54ce0a941440"
},
"updatedAt": "2022-06-26T10:19:22.650Z",
"markers": [
{
"yPercent": "15.853658536585366",
"xPercent": "18.083462132921174",
"desk": {
"images": [
"b85eefee-eeca-4193-87ae-9329fad8256a",
"692743d0-a860-4451-b313-b21a144ef387"
],
"description": "Work like a boss",
"name": "Management desk",
"createdAt": "2022-02-19T21:12:18.832Z",
"updatedAt": "2022-06-07T14:02:13.556Z",
"building": "A",
"code": "01",
"floor": 1,
"relays": [
"1"
],
"board": "932c3e9b-85bd-42c8-9bc0-a318eea7b036"
},
"updatedAt": "2022-06-26T10:19:22.650Z",
"createdAt": "2022-06-26T10:19:22.650Z",
"id": "a8149e84-2f62-46c3-990f-531eff82f6d5"
}
],
"id": "37dc791f-724b-44e2-baaf-bfc606385996"
}
As you can see the 'desk' object doesn't have 'id' field.
Any help would be much appreciated, thanks!
I finally found a workaround.
It looks pretty bad to be fair but it does the trick.
Basically I modified the transform function for the 'desk' document which is the first 'outer' document of my 'inner' document 'image'.
The function looks as follows:
transform: (doc: Desk) => new Desk((new this.deskModel(doc)).toObject({ virtuals: true })),
options: { lean: true },
populate: {
path: 'images',
model: Image,
transform: (doc: Image) => new Image(doc)
}
Basically I needed to inject the corresponding model in the service constructor to be able to call the 'toObject' function with 'options' and 'populate'. Finally I need to use the obtained object to build a new instance of 'Desk' to make sure that class validation functions are applied correctly to my endpoints.
Hope this will help someone and that maybe someone can suggest a more elegant solution.
Related
I want to add an element to the array of all collections in the city collection, but Mongo creates the ID as duplicate.
this is my code
await this.cityRepository.updateMany(
{},
{
$push: {
tags: {
title: tagValue.title,
description: tagValue.description,
applyToAllCity: tagValue.cityId ? false : true,
},
},
},
);
City Schema
export class BaseCity extends Document {
#Prop({
type: String,
required: true,
})
_id: string;
#Prop({ type: String, unique: true })
code: string;
#Prop({ type: String, ref: Province.name })
province: string | Province;
#Prop({ type: String })
faName: string;
}
#Schema({ timestamps: true })
#Schema({ collection: 'city', virtuals: true, _id: false, timestamps: true })
export class City extends BaseCity {
#Prop({ type: String })
imageName: string;
#Prop({ index: true, type: String })
enName: string;
#Prop({ type: Number })
displayOrder: number;
#Prop({ type: Boolean })
isFeatured: boolean;
#Prop({ type: Boolean })
isEnabled: boolean;
#Prop({ type: Coordinate })
coordinate: Coordinate;
#Prop([{ type: Region, ref: Region.name, default: [] }])
region: Region[];
#Prop([{ type: SubMenu }])
subMenu: SubMenu[];
#Prop([{ type: CityTags }])
tags: CityTags[];
}
const CitySchema = SchemaFactory.createForClass(City);
CitySchema.index({ faName: 'text' });
export { CitySchema };
DB
As you can see, ID 63ec8f47efbd82c8face341a is duplicated in all documents.
Is there a solution to solve this problem?
To avoid duplicate IDs, you could use the $addToSet instead of $push. The $addToSet adds an element to an array only if it does not already exist in the set.
Check this:
await this.cityRepository.updateMany(
{},
{
$addToSet: {
tags: {
title: tagValue.title,
description: tagValue.description,
applyToAllCity: tagValue.cityId ? false : true,
},
},
},
);
Update:
To keep unique ids
await this.cityRepository.updateMany(
{},
{
$push: {
tags: {
_id: new ObjectId(),
title: tagValue.title,
description: tagValue.description,
applyToAllCity: tagValue.cityId ? false : true,
},
},
},
);
So I have this entity...
import { Column, Entity, PrimaryColumn } from "typeorm";
#Entity('Users')
export class User {
#PrimaryColumn({ type: 'nvarchar', length: 36, unique: true })
UserId: string;
#Column({ type: 'nvarchar', length: 100, unique: true })
Email: string;
#Column({ type: 'nvarchar', length: 36 })
CrmUserId: string;
#Column({ type: 'nvarchar', length: 36, nullable: true })
AzureB2CUserId: string;
#Column({ type: 'bit', default: false })
IsActive: boolean;
#Column({ type: 'nvarchar', length: 100 })
CreatedBy: string;
#Column({ type: 'datetime' })
CreatedDate: Date;
#Column({ type: 'nvarchar', length: 100, nullable: true })
UpdatedBy: string;
#Column({ type: 'datetime', nullable: true })
UpdatedDate: Date;
}
and using TypeORM I want to get one record by the email, not the UserId. So I had this on the repository.
public async getUserByEmail(email: string): Promise<User | null> {
let _res = await this._userRepository.findOne({ where: { Email: email, IsActive: true }})
return _res;
}
But it always returns a null, even if the record exists, I was thinking of doing it with a CreateQueryBuilder, like this...
public async getUserByEmail(email: string): Promise<User | null> {
let _res = await this._userRepository.createQueryBuilder()
.select()
.from(User, "Users")
.where('email = :emailParameter', { email })
.getOne();
return _res;
}
But the result is the same, I keep getting null, I have no idea what I am doing wrong, because it works if I use the primary key on the findOne and findOneBy. Any help out there with this?
If you are using MongoDB then use import { MongoRepository, Repository } from 'typeorm'; for the _userRepository type.
While using _userRepository findOneBy wrap the userId parameter in import { ObjectId } from 'mongodb' const query = {"_id":ObjectId(req.params.productId)}
OR have a look at this example
know this due to the fact that where in the "entity" you give the default value in the "delete_date" to the user, you must definitely give null.
I wrote in my entity like this
#DeleteDateColumn({
name: 'deleted_date',
type: 'timestamp',
default: null, // default: () => 'CURRENT_TIMESTAMP(6)',
})
public deletedAt: Date;
#CreateDateColumn({
name: 'created_date',
type: 'timestamp',
default: () => 'CURRENT_TIMESTAMP(6)',
})
public createdAt: Date;
#UpdateDateColumn({
name: 'updated_date',
type: 'timestamp',
default: null
onUpdate: 'CURRENT_TIMESTAMP(6)',
})
public updatedAt: Date;
you can't give values to a "delete_date" when you create a user
I'm working on Nestjs project using graphql I want to display the objected nested in my response of graphql as you see below, so I have object comes from socket look like this
socket.emit('sendSettlement', {
bReconcileError: true,
batchNumber: 112233,
btransfered: true,
countryCode: 112233,
currencyCode: 12233,
merchantID: 'd2s13g',
nSettlementAmount: 1122332,
onlineMessageMACerror: true,
reconciliationAdviceRRN: 1122333,
reconciliationApprovalCode: '1122fdsf',
settledTransactions: [
{
appName: '5411fdsb',
cardInputMethod: '6s51b',
cardPAN_PCI: '3f1dbs3df1sss',
networkName: '5d4dsf',
onlineApprovalCode: 11651,
onlineRetrievalReferenceNumber: 6516161,
transactionAmount: 156161,
transactionDate: 'sd3f1g3',
transactionTime: '2f4gB24G',
transacttransactionTypeionTime: 'frqd56r1f',
},
],
settlementAmount: 'ds4f1vd1fv',
settlementDate: '3dfv1',
settlementTime: 'sd3f1vsdf',
terminalID: 'sdRGRD',
traceNumber: 12,
uniqueID: 's56d1rvdr',
});
I saved this to mongoDb correctly but when I want to get this data using graphql I got this Error
{
"errors": [
{
"message": "Cannot return null for non-nullable field Query.findAllSettlement.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"findAllSettlement"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR"
}
}
],
"data": null
}
and the console shows this
{
_id: new ObjectId("62aba86b614eef23deb46cfa"),
bReconcileError: true,
batchNumber: 112233,
btransfered: true,
countryCode: 112233,
currencyCode: 12233,
merchantID: 'd2s13g',
nSettlementAmount: 1122332,
onlineMessageMACerror: true,
reconciliationAdviceRRN: 1122333,
reconciliationApprovalCode: '1122fdsf',
settledTransactions: [ [Object] ],
settlementAmount: 'ds4f1vd1fv',
settlementDate: '3dfv1',
settlementTime: 'sd3f1vsdf',
terminalID: 'sdRGRD',
traceNumber: 12,
uniqueID: 's56d1rvdr',
__v: 0
}
for who wants more info about my code : this is my Schema
export const SettlementSchema = new mongoose.Schema({
bReconcileError: { type: Boolean, required: false },
batchNumber: { type: Number, required: false },
btransfered: { type: Boolean, required: false },
countryCode: { type: Number, required: false },
currencyCode: { type: Number, required: false },
merchantID: { type: String, required: false },
nSettlementAmount: { type: Number, required: false },
onlineMessageMACerror: { type: Boolean, required: false },
reconciliationAdviceRRN: { type: Number, required: false },
reconciliationApprovalCode: { type: String, required: false },
settledTransactions: [
{
appName: String,
cardInputMethod: String,
cardPAN_PCI: String,
networkName: String,
onlineApprovalCode: Number,
onlineRetrievalReferenceNumber: Number,
transactionAmount: Number,
transactionDate: String,
transactionTime: String,
transacttransactionTypeionTime: String,
},
],
settlementAmount: { type: String, required: false },
settlementDate: { type: String, required: false },
settlementTime: { type: String, required: false },
terminalID: { type: String, required: false },
traceNumber: { type: Number, required: false },
uniqueID: { type: String, required: false },
});
this is my Object Type
#ObjectType()
export class SettledTransactionsType {
#Field()
appName: string;
#Field()
cardInputMethod: string;
#Field()
cardPAN_PCI: string;
#Field()
networkName: string;
#Field()
onlineApprovalCode: number;
#Field()
onlineRetrievalReferenceNumber: number;
#Field()
transactionAmount: number;
#Field()
transactionDate: string;
#Field()
transactionTime: string;
#Field()
transactionType: string;
}
#ObjectType()
export class Settlement {
#Field()
bReconcileError: boolean;
#Field()
batchNumber: number;
#Field()
btransfered: boolean;
#Field()
countryCode: number;
#Field()
currencyCode: number;
#Field()
merchantID: string;
#Field()
nSettlementAmount: number;
#Field()
onlineMessageMACerror: boolean;
#Field()
reconciliationAdviceRRN: number;
#Field()
reconciliationApprovalCode: string;
#Field()
settledTransactions: SettledTransactionsType;
#Field()
settlementAmount: string;
#Field()
settlementDate: string;
#Field()
settlementTime: string;
#Field()
terminalID: string;
#Field()
traceNumber: number;
#Field()
uniqueID: string;
}
I want to create a dependency where one User can have many InvestorCase, but one InvestorCase belongs to only one User. I need to have user_id field in InvestorCase.
User entity:
import { InvestorCase } from 'src/investor-case/entities/investor-case.entity';
import { ApiProperty } from '#nestjs/swagger';
import { Exclude } from 'class-transformer';
import {
AllowNull,
Column,
DataType,
Default,
HasMany,
IsIn,
Model,
Table,
} from 'sequelize-typescript';
import { UserRole, UserStatus } from 'src/shared/enums';
import { IUser } from 'src/shared/interfaces';
const userRoleValues = Object.values(UserRole);
const userStatusValues = Object.values(UserStatus);
#Table({ tableName: 'user' })
export class User extends Model<User, IUser> {
#ApiProperty({ example: '1', description: 'User`s Id' })
#Column({
type: DataType.INTEGER,
unique: true,
autoIncrement: true,
primaryKey: true,
})
public id: number;
#ApiProperty({ example: 'test#gmail.com', description: 'User`s Email' })
#Column({
type: DataType.STRING,
allowNull: false,
})
public email: string;
#ApiProperty({ example: 'password', description: 'User``s password' })
#Column({
type: DataType.STRING,
allowNull: true,
})
#Exclude()
public password: string;
#ApiProperty({ example: 'Barak', description: 'User`s name' })
#Column({
type: DataType.STRING,
allowNull: false,
})
public firstName: string;
#ApiProperty({ example: 'Obama', description: 'User`s surname' })
#Column({
type: DataType.STRING,
allowNull: false,
})
public lastName: string;
#ApiProperty({ example: '3806799599432', description: 'User`s phone number' })
#Column({
type: DataType.STRING,
})
public phoneNumber: string;
#ApiProperty({ example: 'verified', description: 'Account status' })
#IsIn({
args: [userStatusValues],
msg: `User status must one of the following:
${userStatusValues.join(', ')}`,
})
#Default(UserStatus.UNVERIFIED)
#Column
public status: UserStatus;
#ApiProperty({
example: 'developer',
description: 'User`s role',
enum: UserRole,
})
#IsIn({
args: [userRoleValues],
msg: `User role must one of the following:
${userRoleValues.join(', ')}`,
})
#Default(UserRole.INVESTOR)
#AllowNull(false)
#Column
public role: UserRole;
#HasMany(() => InvestorCase)
investorCases: InvestorCase[];
}
InvestorCare entity:
import { ApiProperty } from "#nestjs/swagger";
import { BelongsTo, Column, DataType, ForeignKey, IsIn, Model, PrimaryKey, Table } from "sequelize-typescript";
import { PaymentMethods } from 'src/shared/enums'
import { IInvestorCase } from 'src/shared/interfaces';
import { User } from "src/user/entities/user.entity";
const paymentMethods = Object.values(PaymentMethods);
#Table({ tableName: 'investor-case' })
export class InvestorCase extends Model<InvestorCase, IInvestorCase> {
#ApiProperty({ example: '1', description: 'Unique ID' })
#PrimaryKey
#Column({ type: DataType.INTEGER, unique: true, autoIncrement: true })
public id: number;
#ApiProperty({ example: '10000', description: 'The amount the investor will deposit initially.' })
#Column({ type: DataType.INTEGER, unique: true, allowNull: false, validate: { min: 1000 } })
public initialPayment: number;
#ApiProperty({ example: '1000', description: 'The amount that the investor will contribute monthly.' })
#Column({ type: DataType.INTEGER, allowNull: true, validate: { min: 500 } })
public monthlyPayment: number;
#ApiProperty({
example: 'true',
description: 'The payment method by which the investments will be made.',
enum: paymentMethods
})
#IsIn({
args: [paymentMethods],
msg: `The payment method must one of the following: ${paymentMethods.join(',')}`
})
#Column({ type: DataType.STRING, allowNull: false, defaultValue: PaymentMethods.Manually })
public paymentMethod: string;
#BelongsTo(() => User, {
foreignKey: 'userId',
as: 'UserId',
})
#ApiProperty({
example: '1',
description: 'Company representative user id',
})
#ForeignKey(() => User)
#Column({ type: DataType.INTEGER })
userId: number;
}
I try to create InvestorCase using this:
{
"initialPayment": 5000,
"monthlyPayment": 1000,
"paymentMethod": "Link a bank account",
"userId": 2
}
[Nest] 244 - 05/10/2022, 10:30:26 AM ERROR [ExceptionsHandler] Validation error
Error:
at Query.run (/app/node_modules/sequelize/src/dialects/postgres/query.js:76:25)
at /app/node_modules/sequelize/src/sequelize.js:643:28
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at PostgresQueryInterface.insert (/app/node_modules/sequelize/src/dialects/abstract/query-interface.js:773:21)
at InvestorCase.save (/app/node_modules/sequelize/src/model.js:4046:35)
at Function.create (/app/node_modules/sequelize/src/model.js:2253:12)
at InvestorCaseService.create (/app/src/investor-case/investor-case.service.ts:18:16)
at InvestorCaseController.create (/app/src/investor-case/investor-case.controller.ts:18:16)
at /app/node_modules/#nestjs/core/router/router-execution-context.js:46:28
at /app/node_modules/#nestjs/core/router/router-proxy.js:9:17
But alwways got error:
[Nest] 244 - 05/10/2022, 10:30:26 AM ERROR [ExceptionsHandler] Validation error
Error:
at Query.run (/app/node_modules/sequelize/src/dialects/postgres/query.js:76:25)
at /app/node_modules/sequelize/src/sequelize.js:643:28
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at PostgresQueryInterface.insert (/app/node_modules/sequelize/src/dialects/abstract/query-interface.js:773:21)
at InvestorCase.save (/app/node_modules/sequelize/src/model.js:4046:35)
at Function.create (/app/node_modules/sequelize/src/model.js:2253:12)
at InvestorCaseService.create (/app/src/investor-case/investor-case.service.ts:18:16)
at InvestorCaseController.create (/app/src/investor-case/investor-case.controller.ts:18:16)
at /app/node_modules/#nestjs/core/router/router-execution-context.js:46:28
at /app/node_modules/#nestjs/core/router/router-proxy.js:9:17
I've shops and products collections in Mongodb.
Shops Schema
#Schema({ timestamps: true })
export class Shop extends Document {
#Prop({ required: true, index: { unique: true } })
id: string;
#Prop({ required: true, index: { unique: true } })
shop: string;
#Prop({ required: true })
accessToken: string;
#Prop({ required: true })
scope: string;
#Prop({ type: Date })
expires: Date;
#Prop({ type: Boolean })
isOnline: boolean;
#Prop({ type: String })
state: string;
}
export const ShopSchema = SchemaFactory.createForClass(Shop);
Products Schema
#Schema({ timestamps: true })
export class Product extends Document {
#Prop({ required: true, index: {unique: true} })
id: string
#Prop({ required: true })
title: string
#Prop({ required: true })
desc: string
#Prop({ required: true })
price: number
#Prop({ required: true })
stock: number
#Prop({ type: mongooseSchema.Types.ObjectId, ref: 'Shop', required: true })
shopId: mongooseSchema.Types.ObjectId;
}
export const ProductSchema = SchemaFactory.createForClass(Product);
Now I want to seed some dummy data (from json) into products collection whenever new shop is added. I've done using Nestjs Command and it worked perfectly. But I don't want to seed data by using command, so I tried on Nestjs lifecycle event method (which is not a proper way to do). Can anyone recommend me any other method?
I heard we can do using Mongoose hooks. If we can, please anyone explain about it.