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;
}
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,
},
},
},
);
I create a user entity with password:
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column({ type: "text", nullable: false })
userName: string;
#Column({ type: "text", nullable: false })
email: string;
#Column({ type: "text", nullable: false })
firstName: string;
#Column({ type: "text", nullable: false })
lastName: string;
#Column({ type: "text", nullable: false, select: false }) //It is select as false
password: string;
#Column({ type: "text", nullable: true })
socket_id: string;
#Column({ type: "text", nullable: true })
avatar: string;
#Column({ type: "boolean", default: false, nullable: false })
is_verify: boolean;
#CreateDateColumn()
created_at: Date;
#UpdateDateColumn()
updated_at: Date;
}
In this entity, I set select as false for password. When I find it, don't see the password column. How can I find this column?
const user = await this.userRepository.findOneBy({
email: loginInput.email,
is_verify: true
})
console.log(user)
What things I have to change in this code to get password column?
You can select hidden column:
const user = await this.userRepository.findOne({
where: {
email: loginInput.email,
is_verify: true,
},
select:{
password:true,
},
});
console.log(user);
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.
I have a function which suscribes a "userId" to a threadId like following:
suscribeToThread: async (threadId: IThread["_id"], userId: IUser["_id"]) => {
return await threadModel.updateOne(
{ _id: threadId },
{ $addToSet: { suscribers: userId } }
);
},
To which I get the following error:
Type '{ suscribers: string; }' is not assignable to type '{ readonly [x: string]: any; readonly [x: number]: any; } & NotAcceptedFields<_AllowStringsForIds<LeanDocument<any>>, readonly any[]> & { readonly [key: string]: any; } & { readonly id?: any; ... 4 more ...; readonly replies?: string | AddToSetOperators<...>; } & NotAcceptedFields<...>'.
Type '{ suscribers: string; }' is not assignable to type 'NotAcceptedFields<_AllowStringsForIds<LeanDocument<any>>, readonly any[]>'.
Property 'suscribers' is incompatible with index signature.
Type 'string' is not assignable to type 'never'.ts(2322)
This error only happens with $addToSet, $push and $pull operators.
Here is the model/interface for Thread Model
import mongoose, { Document, Schema } from "mongoose";
import { IComment } from "../comment/commentModel";
import { IUser } from "../user/userModel";
export interface IThread extends Document {
_id: string;
title: string;
timestamp: number;
author: IUser["_id"];
content: string;
locked: boolean;
sticky: boolean;
likedBy: Array<IUser["_id"]>;
dislikedBy: Array<IUser["_id"]>;
viewedBy: Array<IUser["_id"]>;
suscribers: Array<IUser["_id"]>;
replies: Array<IComment["_id"]>;
}
const ThreadSchema = new mongoose.Schema({
title: String,
timestamp: { type: Date, default: Date.now },
author: { type: Schema.Types.ObjectId, ref: "User" },
content: String,
locked: { type: Boolean, default: false },
sticky: { type: Boolean, default: false },
likedBy: [{ type: Schema.Types.ObjectId, ref: "User", default: [] }],
dislikedBy: [{ type: Schema.Types.ObjectId, ref: "User", default: [] }],
viewedBy: [{ type: Schema.Types.ObjectId, ref: "User", default: [] }],
suscribers: [{ type: Schema.Types.ObjectId, ref: "User", default: [] }],
replies: [{ type: Schema.Types.ObjectId, ref: "Comment", default: [] }],
});
export default mongoose.models.Thread ||
mongoose.model<IThread>("Thread", ThreadSchema);
I can ignore the error with ts-ignore and everything works fine, but I don't think this is the correct way to go. Any help would be appreciated!
suscribeToThread function:
import mongoose from "mongoose";
suscribeToThread: async (threadId: IThread["_id"], userId: IUser["_id"]) => {
return await threadModel.updateOne(
{ _id: threadId },
{ $addToSet: { suscribers: new mongoose.Types.ObjectId(userId) } }
);
}
thread model:
import mongoose, { Document, Schema } from "mongoose";
import { IComment } from "../comment/commentModel";
import { IUser } from "../user/userModel";
export interface IThread extends Document {
_id: string;
title: string;
timestamp: number;
author: IUser["_id"];
content: string;
locked: boolean;
sticky: boolean;
likedBy: Array<IUser["_id"]>;
dislikedBy: Array<IUser["_id"]>;
viewedBy: Array<IUser["_id"]>;
suscribers: Array<IUser["_id"]>;
replies: Array<IComment["_id"]>;
}
const ThreadSchema = new mongoose.Schema({
title: String,
timestamp: { type: Date, default: Date.now },
author: { type: Schema.Types.ObjectId, ref: "User" },
content: String,
locked: { type: Boolean, default: false },
sticky: { type: Boolean, default: false },
likedBy: {
type: [mongoose.Types.ObjectId],
ref: "User",
default: []
},
dislikedBy: {
type: [mongoose.Types.ObjectId],
ref: "User",
default: []
},
viewedBy: {
type: [mongoose.Types.ObjectId],
ref: "User",
default: []
},
suscribers: {
type: [mongoose.Types.ObjectId],
ref: "User",
default: []
},
replies: {
type: [mongoose.Types.ObjectId],
ref: "User",
default: []
}
});
export default mongoose.models.Thread || mongoose.model < IThread > ("Thread", ThreadSchema);
You need to convert userId into ObjectId. It is of type string, so typescript is throwing error.
I have this model
import {Entity, model, property} from '#loopback/repository';
#model()
export class Coupon extends Entity {
#property({
id: true,
type: 'string',
required: false,
mongo: {
columnName: '_id',
dataType: 'ObjectID',
},
})
id: string;
#property({
type: 'string',
required: true,
})
name: string;
#property({
type: 'number',
required: true,
})
maximumUses: number;
#property({
type: 'string',
required: true,
})
type: string;
#property({
type: 'number',
required: true,
})
amount: number;
#property({
type: 'number',
required: true,
})
maximumUsesPerPerson: number;
#property({
type: 'string',
required: true,
})
validFrom: string;
#property({
type: 'string',
required: true,
})
validTo: string;
#property({
type: 'number',
required: true,
})
currentTotalUses: number;
#property({
type: 'array',
itemType: 'string',
})
certainDays?: string[];
#property({
type: 'array',
itemType: 'string',
})
certainHours?: string[];
#property({
type: 'boolean',
required: true,
})
valid: boolean;
#property({
type: 'array',
itemType: 'string',
})
clients?: string[];
#property({
type: 'disabled',
required: true,
})
disabled: boolean;
constructor(data?: Partial<Coupon>) {
super(data);
}
}
repository for the model
import {DefaultCrudRepository} from '#loopback/repository';
import {Coupon} from '../models';
import {TestDataSource} from '../datasources';
import {inject} from '#loopback/core';
export class CouponRepository extends DefaultCrudRepository<
Coupon,
typeof Coupon.prototype.id
> {
constructor(
#inject('datasources.test') dataSource: TestDataSource,
) {
super(Coupon, dataSource);
}
}
now the following function should works well
await this.couponsRepo.create({ name: 'string',
maximumUses: 0,
maximumUsesPerPerson: 0,
amount: 0,
validFrom: 'string',
validTo: 'string',
type: 'percentage',
valid: true,
currentTotalUses: 0,
disabled: false });
but it fires this error
ReferenceError: g is not defined
at new disabled (eval at createModelClassCtor (../LBIssue/lbissue/node_modules/loopback-datasource-juggler/lib/model-builder.js:678:21), :10:27)
to simply produce this error , create empty loopback 4 project
then put the coupon model = with the code I provided
There is an error in your model definition.
See this
#property({
type: 'disabled',
required: true,
})
disabled: boolean;
type cannot be disabled. It should be
#property({
type: 'boolean',
required: true,
})
disabled: boolean;