Getting related data with NestJS and Mongo - mongodb

I'm new to NestJS and Mongo. I'm defining the schemes and I'm trying to get the data of related collections. I followed the examples from here but I don't get it why is not working in my code. I saw another example in StackOverflow with no success.
What I get:
[
{
"songs": [],
"_id": "6066d3783b1f96201a62372a",
"name": "The best album",
"year": 2021,
"image": "cover"
}
]
What I'm trying to get:
[
{
"songs": [
{
"_id": "6066d3e13b1f96201a62372b",
"name": "Song1"
},
{
"_id": "6066d46a3b1f96201a62372c",
"name": "Song2"
},
],
"_id": "6066d3783b1f96201a62372a",
"name": "The best album",
"year": 2021,
"image": "cover"
}
]
Album Document
{
"_id": {
"$oid": "6066d3783b1f96201a62372a"
},
"name": "The best album",
"year": {
"$numberInt": "2021"
},
"image": "cover"
}
Song Document
{
"_id": {
"$oid": "6066d3e13b1f96201a62372b"
},
"name": "Song1",
"album": {
"$oid": "6066d3783b1f96201a62372a"
}
},
{
"_id": {
"$oid": "6066d46a3b1f96201a62372c"
},
"name": "Song2",
"album": {
"$oid": "6066d3783b1f96201a62372a"
}
}
album.module.ts
import { Module } from '#nestjs/common';
import { AlbumsController } from './albums.controller';
import { AlbumsService } from './albums.service';
import { MongooseModule } from '#nestjs/mongoose';
import { Album, AlbumSchema } from './schemas/album.schema';
#Module({
imports: [MongooseModule.forFeature([{ name: Album.name, schema: AlbumSchema }])],
controllers: [AlbumsController],
providers: [AlbumsService],
})
export class AlbumModule {}
albums.controller.ts
import { Controller, Get, HttpStatus, Res } from '#nestjs/common';
import { AlbumsService } from './albums.service';
#Controller('albums')
export class AlbumsController {
constructor(private readonly albumsService: AlbumsService) {}
#Get()
async getAlbums(#Res() res) {
const albums = await this.albumsService.getAlbums();
return res.status(HttpStatus.OK).json(albums);
}
}
albums.service.ts
import { Model } from 'mongoose';
import { Injectable } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { Album, AlbumDocument } from './schemas/album.schema';
#Injectable()
export class AlbumsService {
constructor(#InjectModel(Album.name) private albumModel: Model<AlbumDocument>) {}
async getAlbums(): Promise<Album[]> {
return this.albumModel.find().populate('songs').exec();
}
}
album.schema.ts
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Song } from './song.schema';
import { Document, Types } from 'mongoose';
import * as mongoose from 'mongoose';
export type AlbumDocument = Album & Document;
#Schema()
export class Album {
#Prop()
name: string;
#Prop()
year: number;
#Prop()
image: string;
#Prop([{ type: mongoose.Schema.Types.ObjectId, ref: 'Song' }])
songs: Song[];
}
export const AlbumSchema = SchemaFactory.createForClass(Album);
song.schema.ts
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Album } from './album.schema';
import { Document } from 'mongoose';
import * as mongoose from 'mongoose';
export type SongDocument = Song & Document;
#Schema()
export class Song {
#Prop()
name: string;
#Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Album' })
album: Album;
}
export const SongSchema = SchemaFactory.createForClass(Song);
Thank you in advance.

Related

Enable to set ObjectId in nested array in nest.js

I have a module of question, and I want a structure something like
[ { question: 'REFERENCE ID', answer: { start_value: '1', end_value: '2' } } ],
when I try to save it, it save question id as string but I want to store questionId as ObjectId so I can populate it in other request.
while and the complete response I want is:
{
"answer_by": ObjectId(''), // string, should be user reference ID for populating user
"answers": [
{
"question": ObjectId(''), // string, should be user reference ID for populating question
"answer": {
"start_value": "val1",
"end_value": "val2"
}
}
]
}
here is my schema respectively!
import * as mongoose from "mongoose";
import { Person } from "./person.schema";
import { PostMeetingChecklist, PostMeetingChecklistDocument } from "./post-meeting-checklist.schema";
import { Model, Schema as MongooseSchema } from "mongoose";
import { Resource } from "./resource.schema";
#Schema()
export class Answer {
#Prop()
start_value: string;
#Prop()
end_value: string;
}
#Schema()
export class Item {
#Prop({ type: mongoose.Schema.Types.ObjectId, ref: Question.name})
question: string;
#Prop()
answer: Answer;
}
export type QuestionDocument = Question & mongoose.Document;
#Schema({ timestamps: true })
export class Question {
#Prop({ type: mongoose.Schema.Types.ObjectId, ref: User.name })
answer_by: string;
#Prop()
answers: [Item];
}
export const QuestionSchema = SchemaFactory.createForClass(Question);
in mongoDb,it takes answer_by as ObjectId but question as string, how to fixed that?
Further, I also want to populate this like:
{
"answer_by": User Details,
"answers": [
{
"question": Question Details,
"answer": {
"start_value": "5",
"end_value": "10"
}
}
],
"_id": "63f3146f9f84a58be0b32ad8",
"createdAt": "2023-02-20T06:34:23.300Z",
"updatedAt": "2023-02-20T06:34:23.300Z",
"__v": 0
}
Have tried
#Prop()
answers: [{
question:{
type:mongoose.Schema.Types.ObjectId,
ref:"PostMeetingChecklist"
},
answer: Answer
}];
but in vain.

How to implement multiple query condition with Spring Data MongDB?

I'm starting learn spring data mongodb. Currently, I try to implement multiple condition query with Spring Data MongoDB.
First I have a sample collection
{
"_id": "df05532f-8893-4802-8710-ab92056c9c77",
"objectId": {
"$numberLong": "1"
},
"creator": {
"_id": {
"$numberLong": "3"
},
"username": "magic"
},
"content": "7878787887",
"attachments": [],
"pinned": false,
"histories": [
{
"content": "new new comment 2222",
"createdAt": {
"$date": {
"$numberLong": "1664970753576"
}
}
},
{
"content": "update me",
"createdAt": {
"$date": {
"$numberLong": "1664970753691"
}
}
},
{
"content": "update me3333",
"createdAt": {
"$date": {
"$numberLong": "1664970753734"
}
}
},
{
"content": "44444455666",
"createdAt": {
"$date": {
"$numberLong": "1664970753740"
}
}
}
],
"contentUpdateAt": {
"$date": {
"$numberLong": "1664970753745"
}
},
"createdAt": {
"$date": {
"$numberLong": "1664970753576"
}
},
"lastModifiedAt": {
"$date": {
"$numberLong": "1664970753772"
}
}
}
I'm trying to find a partial document when the array histories has one element that matched with content equals 44444455666
With MongoDB CLI, I completed build the query like
db.ticket_detail_comments.find(
{
histories: {
$elemMatch: {
content: "44444455666"
}
},
_id : "df05532f-8893-4802-8710-ab92056c9c77"
},
{ "histories.$": 1, name: 1 }
)
The result was exactly what I expected
{
"_id": "df05532f-8893-4802-8710-ab92056c9c77",
"histories": [
{
"content": "44444455666",
"createdAt": {
"$date": {
"$numberLong": "1664970753740"
}
}
}
]
}
When I tried to build query with Spring Data MongoDB (using MongoDbTemplate) I can't find any way to build query look like in console. I tried addOperation but seem not work,
I tried to build 2 separated Criteria (one, two) that match with 2 condition I expected, then combine them with andOperation like new Criteria().andOperator(one).andOperator(two);. The unhappy part is the query generated is so different
db.ticket_detail_comments.find(
{
"$and": [
{
histories: {
$elemMatch: {
content: "44444455666"
}
},
_id : "df05532f-8893-4802-8710-ab92056c9c77"
},
{ "histories.$": 1, name: 1 }
]
}
)
With this query, I always get a null value. Can anyone help me with that? What is correct Criteria syntax I should use?
Thanks for reading.
UPDATE
I created a small demo, let's see
Data in Mongo Collection look like
{
"_id": "dad9db98-241b-40fa-aab4-af1671f97631",
"data": "demo",
"children": [
{
"name": "tim",
"age": 19
},
{
"name": "alex",
"age": 18
}
],
"_class": "com.example.demo.Data"
}
MONGOCLI
db.data.find(
{
children: {
$elemMatch: {
name: "alex"
}
},
_id : "dad9db98-241b-40fa-aab4-af1671f97631"
},
{ "children.$": 1}
)
=> Result:
{ _id: 'dad9db98-241b-40fa-aab4-af1671f97631',
children: [ { name: 'alex', age: 18 } ] }
JAVA CODE
package com.example.demo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.UUID;
#SpringBootApplication
public class DemoApplication {
private final MongoTemplate template;
public DemoApplication(MongoTemplate template) {
this.template = template;
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#PostConstruct
public void init() {
Data.Child child1 = Data.Child.builder()
.name("alex")
.age(18)
.build();
Data.Child child2 = Data.Child.builder()
.name("tim")
.age(19)
.build();
final String id = UUID.randomUUID().toString();
Data data = Data.builder()
.id(id)
.data("demo")
.children(List.of(child2, child1))
.build();
template.save(data, "data");
Query query = Query.query(
Criteria.where("_id").is(id).and("children")
.elemMatch(Criteria.where("name").is("alex"))
);
query.fields().position("children", 1);
var r = template.findOne(query, Data.class, "data");
System.out.printf("DEBUG");
}
}
#lombok.Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
class Data {
private String id;
private String data;
private List<Child> children;
#lombok.Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
static class Child {
private String name;
private Integer age;
}
}
=> Result: Data Object with 2 elements in children list.
But what I expected is Data Object has one element in children list like resut from CLI

How to accept multiple objects into an array NestJS

I have a feedbackQuestion schema which takes (title: string, subtitle: string, types: enum, values: enum)
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose'
import { Document } from 'mongoose'
import { Types, Value } from 'src/common/enum/types.enum'
export type FeedbackQuestionDocument = FeedbackQuestion & Document
#Schema({ timestamps: true, id: true })
export class FeedbackQuestion {
#Prop()
title: string
#Prop()
subtitle: string
#Prop()
types: Types
#Prop()
value: Value
}
export const FeedbackQuestionSchema =
SchemaFactory.createForClass(FeedbackQuestion)
The feedbackQuestion schema serves as a subdocument in my feedback schema for the key question
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose'
import mongoose, { Document, ObjectId } from 'mongoose'
import { User } from './user.schema'
import { Transform, Type } from 'class-transformer'
import { FeedbackQuestion } from './feedback-question.schema'
import { distributionChannels } from 'src/common/enum/distributionChannels.enum'
export type FeedbackDocument = Feedback & Document
#Schema({ timestamps: true, id: true })
export class Feedback {
#Transform(({ value }) => value.toString())
_id: ObjectId
#Prop()
label: string
#Prop({ default: false })
status: boolean
#Prop()
question: [FeedbackQuestion]
#Prop()
comment: string
#Prop()
thankYouMessage: string
#Prop()
distributionChannels: distributionChannels
#Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' })
#Type(() => User)
user: User
}
export const FeedbackSchema = SchemaFactory.createForClass(Feedback)
when creating my create-feedbackDto, I assigned question to be an array of type feedbackQuestion
import { Type } from 'class-transformer'
import { FeedbackQuestion } from '../../schemas/feedback-question.schema'
import { IsArray, IsEnum, IsNotEmpty, ValidateNested } from 'class-validator'
import { Types, Value } from '../enum/types.enum'
import { distributionChannels } from '../enum/distributionChannels.enum'
export class CreateFeedbackDto {
#IsNotEmpty()
label: string
status: boolean
#IsArray()
#ValidateNested({ each: true })
#Type(() => FeedbackQuestion)
question: FeedbackQuestion[]
#IsNotEmpty()
comment: string
#IsNotEmpty()
thankYouMessage: string
#IsNotEmpty()
title: string
#IsNotEmpty()
subtitle: string
#IsEnum(Types)
#IsNotEmpty()
types: Types
#IsEnum(Value)
#IsNotEmpty()
value: Value
#IsEnum(distributionChannels)
#IsNotEmpty()
distributionChannels: distributionChannels
}
In my feedback services, I want to work on questions such that I can pass in multiple objects of feedbackQuestion into the question array when creating a feedback. Please How can I do that?
The current code only takes one FeedbackQuestion object in the array
import { Injectable } from '#nestjs/common'
import { InjectModel } from '#nestjs/mongoose'
import { Model } from 'mongoose'
import { Feedback, FeedbackDocument } from '../../schemas/feedback.schema'
import {
FeedbackQuestion,
FeedbackQuestionDocument,
} from '../../schemas/feedback-question.schema'
import { IServiceResponse } from '../../common/interfaces/service.interface'
import { CreateFeedbackDto } from 'src/common/dto/create-feedback.dto'
#Inject#5406able()
export class FeedbackService {
constructor(
#Inject#5406Model(Feedback.name)
private feedbackDocumentModel: Model<FeedbackDocument>,
#Inject#5406Model(FeedbackQuestion.name)
private feedbackQuestionDocumentModel: Model<FeedbackQuestionDocument>,
) {}
async createFeedback(payload: CreateFeedbackDto): Promise<IServiceResponse> {
const feedbackQuestion = await this.feedbackQuestionDocumentModel.create({
title: payload.title,
subtitle: payload.subtitle,
type: payload.types,
value: payload.value,
})
const feedback = await this.feedbackDocumentModel.create({
label: payload.label,
status: payload.status,
question: [feedbackQuestion],
comment: payload.comment,
thankYouMesage: payload.thankYouMessage,
distributionChannels: payload.distributionChannels,
})
// feedback.question.push(feedbackQuestion)
await feedback.save()
return {
data: {
user: feedback,
},
}
}
}
This is the current response I get
"label": "Yearly Feedback",
"status": false,
"question": [
{
"title": "Yearly Feedback",
"subtitle": "Rating the Yearly performance of the organization",
"value": 1,
"_id": "627fa9b915d31bbbc0fe6908",
"createdAt": "2022-05-14T13:08:09.180Z",
"updatedAt": "2022-05-14T13:08:09.180Z",
"__v": 0
}
],
Thanks

How can I send GeoJSON polygon data into typeorm (NestJS)?

I want to send GeoJSON polygon data into PostgresSQL by POST request.
Therefore, I have tried to receive Position[][] type and convert it into a Polygon type then send a POST request by Postman for API test but I got an error: "QueryFailedError: Unable to find 'coordinates' in GeoJSON string".
There are my codes:
Entity
import { Column, Entity, Index, PrimaryGeneratedColumn } from "typeorm";
import { Polygon } from "geojson";
#Entity({ name: 'parcels' })
export class Parcel {
#PrimaryGeneratedColumn('uuid')
id: string
#Index({ spatial: true })
#Column({
type: 'geography',
spatialFeatureType: 'Polygon',
srid:4326,
nullable: true
})
polygon: Polygon
}
Dto
import { IsOptional } from "class-validator";
import { Position } from "geojson";
export class CreateParcelPointDto {
#IsOptional()
position?: Position[][]
}
Controller
import { Body, Controller, Post } from '#nestjs/common';
import { CreateParcelPointDto } from './dto/create-parcel-point.dto';
import { Parcel } from './parcel.entity';
import { ParcelService } from './parcel.service';
#Controller('parcels')
export class ParcelController {
constructor(private parcelService: ParcelService) {}
#Post()
async createParcelPoint(
#Body()
createParcelPointDto: CreateParcelPointDto
): Promise<Parcel> {
return this.parcelService.createParcelPoint(createParcelPointDto)
}
}
Service
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Polygon } from 'geojson';
import { CreateParcelPointDto } from './dto/create-parcel-point.dto';
import { ParcelRepository } from './parcel.repository';
import { Parcel } from './parcel.entity';
#Injectable()
export class ParcelService {
constructor(
#InjectRepository(ParcelRepository)
private parcelRepository: ParcelRepository
) {}
async createParcelPoint(createParcelPointDto: CreateParcelPointDto): Promise<Parcel> {
const { position } = createParcelPointDto
const polygon: Polygon = {
type: 'Polygon',
coordinates: position
}
const parcel = this.parcelRepository.create({
polygon,
})
await this.parcelRepository.save(parcel)
return parcel
}
}
POST request JSON
{
"polygon" : [
[ 102.016680916961207, 14.876721809875564 ],
[ 102.016926580451127, 14.876676829236565 ],
[ 102.016936960598585, 14.876688939408604 ],
[ 102.017125533277465, 14.876656068941644 ],
[ 102.017130723351187, 14.876638768695875 ],
[ 102.017360816619913, 14.876598978130607 ],
[ 102.017243174948689, 14.87595713901259 ],
[ 102.017000971507926, 14.876002119651588 ],
[ 102.016994051409625, 14.875983089381243 ],
[ 102.016789908509551, 14.876022879946511 ],
[ 102.016786448460394, 14.876047100290586 ],
[ 102.016559815240825, 14.876090350905008 ],
[ 102.016680916961207, 14.876721809875564 ]
],
}
I don't know how to handle GeoJSON type into typeorm by POST request. If anyone has some solution please help me.
I had solved the problem. In this case, you need to define the type in Dto as the number. The dimension must be the same as the Position type and increase one more dimension in coordinates while you create a Polygon object.
For example, a Polygon type is needed to receive coordinate in Position[][]. Therefore, You need to define polygon as type number[][] in Dto and define coordinates as [polygon] while you create Polygon objects.
Code example :
Dto
import { IsOptional } from "class-validator";
export class CreateParcelPointDto {
#IsOptional()
polygon?: number[][]
}
Service
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Polygon } from 'geojson';
import { CreateParcelPointDto } from './dto/create-parcel-point.dto';
import { ParcelRepository } from './parcel.repository';
import { Parcel } from './parcel.entity';
#Injectable()
export class ParcelService {
constructor(
#InjectRepository(ParcelRepository)
private parcelRepository: ParcelRepository
) {}
async createParcelPoint(createParcelPointDto: CreateParcelPointDto): Promise<Parcel> {
const {
polygon,
} = createParcelPointDto
const polygonCreated: Polygon = {
type: 'Polygon',
coordinates: [polygon] //Need one more dimension here.
}
const parcel = this.parcelRepository.create({
polygon: polygonCreated,
})
await this.parcelRepository.save(parcel)
return parcel
}
}

What is the mongoose schema of this json example?

I am working on a Node.js project with MongoDb Database .
I need the schema of this json example :
I am working on a Node.js project with MongoDb Database .
I need the schema of this json example
MongoDb document :
{
"container_number": "ddd",
"container_Date": "2/2/2018",
"scannedProductArray": {
"CCR": [
{
"ScannedDate": {
"$date": "2018-03-28T20:54:57.663Z"
},
"productNumber": "4656874974",
"productType": "CCR"
},
{
"ScannedDate": {
"$date": "2018-03-28T20:55:23.698Z"
},
"productNumber": "4656874974",
"productType": "CCR"
}
],
"CCH": [
{
"ScannedDate": {
"$date": "2018-03-28T21:25:16.202Z"
},
"productNumber": "4656874974",
"productType": "CCR"
},
{
"ScannedDate": {
"$date": "2018-03-28T21:26:08.696Z"
},
"productNumber": "4656874974",
"productType": "CCR"
}
]
}
}
container_number: String,
container_Date: String,
scannedProductArray:{CCR:[ScannedDate: {
date:type:Date,default:Date.now
},
"productNumber:Number,
"productType": "String"],CCH[:[ScannedDate: {
date:type:Date,default:Date.now
},
"productNumber:Number,
"productType": "String"]}
May be this one helps you.
I'd like to define schemas like this:
const Product = {
ScannedDate: {
type: Object,
},
productNumber: {
type: String,
},
productType: {
type: String,
default: 'CCR',
}
};
const Item = {
itemName: {
type: [Product],
},
};
const Container = {
container_number: {
type: String,
},
container_Date: {
type: String
},
scannedProductArray: {
type: Object, // Item
}
};
If the CCR/CCH fields are dynamic, I can just use type: Object rather than a certain structure. And I validate these array items by myself instead of mongoose.