Swagger - API call with multiple paths - node.js

I'm trying make a swagger file for an api GET request that has several paths to it. There's the GET /listings, GET /listings that passes in req.query.bidderId and another one that passes req.query.watcherId and one for req.query.winnerId. Currently swagger is saying
Additional properties not allowed: /listings/{winnerId},/listings/{watcherId}
I'm not really sure how to handle all the different scenarios for one API call in swagger file?
Do I only need to have one with /{id}: and just let the express code do the rest? Is it that simple?
app.js
//GETS AUCTION LISTINGS
app.get("/api/listings", (req, res, next) => {
query = {};
if (req.query.bidderId && req.query.winner === "false") {
query = { bidderId: req.query.bidderId };
biddingGroupQuery = { biddingGroup: req.query.biddingGroup }
Post.find({
$and: [
{ biddingGroup: { $in: [req.query.bidderId] } },
{ auctionEndDateTime: { $gte: Date.now() } }
]
})
.populate("creator", "username")
.then(documents => {
req.params.Id = mongoose.Types.ObjectId(req.params.Id);
res.status(200).json({
message: "Active Bids Listings retrieved successfully!",
posts: documents
});
});
} else if (req.query.bidderId && req.query.winner) {
query = { bidderId: req.query.bidderId };
biddingGroupQuery = { biddingGroup: req.query.biddingGroup };
Post.find({
$and: [
{ bidderId: req.query.bidderId },
{ auctionEndDateTime: { $lte: Date.now() } }
]
})
.populate("creator", "username")
.then(documents => {
req.params.Id = mongoose.Types.ObjectId(req.params.Id);
res.status(200).json({
message: "Auction listings retrieved successfully!",
posts: documents
});
});
} else if (req.query.watcherId) {
Post.find({
$and: [
{ watchingGroup: { $in: [req.query.watcherId] } },
{ auctionEndDateTime: { $gte: Date.now() } }
]
})
.populate("creator", "username")
.then(documents => {
res.status(200).json({
message: "User's watchlist items retrieved!",
posts: documents
});
});
} else if (!req.query.bidderId) {
Post.find({ auctionEndDateTime: { $gte: Date.now() } })
.populate("creator", "username")
//.populate('watchlist', 'watchItemUniqueKey')
.then(documents => {
req.params.Id = mongoose.Types.ObjectId(req.params.Id);
res.status(200).json({
message: "Auction listings retrieved successfully!",
posts: documents
});
});
}
});
swagger file
swagger: "2.0"
info:
version: "1.0.0"
title: Hello World App during dev, should point to your local machine
basePath: /v1
schemes:
# tip: remove http to make production-grade
- http
- https
paths:
/listings:
x-swagger-router-controller: listings
get:
description: Return all the auction listings
operationId: getAuctions
responses:
"200":
description: Success got all the listings
schema:
$ref: "/definitions/Listing"
"500":
description: Unexpected Error
schema:
type: object
properties:
message:
type: string
/listings/{id}:
x-swagger-router-controller: listings
get:
description: My Bids, My Wins, and My Watchlist GET request
operationId: getId
parameters:
- in: path
name: id
required: true
type: string
minimum: 5
description: BidderId, WatcherId, or WinnerId
responses:
"200":
description: Success got all the listings
schema:
$ref: "/definitions/Listing"
"500":
description: Unexpected Error
schema:
type: object
properties:
message:
type: string
definitions:
Listing:
properties:
id:
type: integer
glassTitle:
type: boolean
startingBid:
type: string
increments:
type: string
shippingCost:
type: string
auctionType:
type: string
buyItNow:
type: string
snipingRules:
type: string
creator:
type: string
username:
type: string
biddingGroup:
type: array
items:
type: string
watchingGroup:
type: array
items:
type: string
lastBidderName:
type: string
currentBid:
type: string

Related

Cast to ObjectId failed for value at path for model error

This is my Profile Schema:
const mongoose = require('mongoose');
const ProfileSchema = new mongoose.Schema({
user: {
// Special field type because
// it will be associated to different user
type: mongoose.Schema.Types.ObjectId,
ref: 'user',
},
company: {
type: String,
},
website: {
type: String,
},
location: {
type: String,
},
status: {
type: String,
required: true,
},
skills: {
type: [String],
required: true,
},
bio: {
type: String,
},
githubusername: {
type: String,
},
experience: [
{
title: {
type: String,
required: true,
},
company: {
type: String,
required: true,
},
location: {
type: String,
},
from: {
type: Date,
required: true,
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false,
},
description: {
type: String,
},
},
],
education: [
{
school: {
type: String,
required: true,
},
degree: {
type: String,
required: true,
},
fieldofstudy: {
type: String,
required: true,
},
from: {
type: Date,
required: true,
},
to: {
type: Date,
},
current: {
type: Boolean,
default: false,
},
description: {
type: String,
},
},
],
social: {
youtube: {
type: String,
},
twitter: {
type: String,
},
facebook: {
type: String,
},
linkedin: {
type: String,
},
instagram: {
type: String,
},
},
date: {
type: Date,
default: Date.now,
},
});
module.exports = Profile = mongoose.model('profile', ProfileSchema);
This is my view api. It doesn't work. it only return Cast to ObjectId failed for value { 'experience._id': '5edcb6933c0bb75b3c90a263' } at path _id for model profile
router.get('/experience/viewing/:viewexp_id', auth, async (req, res) => {
try {
const exp = await Profile.findById({
'experience._id': req.params.viewexp_id,
});
if (!exp) {
return res.status(404).json({ msg: 'Experience not found' });
}
res.json(exp);
} catch (err) {
console.error(err.message);
res.status(500).send(err.message);
}
});
How can I fix this? I tried looking at the stackoverflow of the same errors. still it doesn't seem to work.
and this is what I am trying to hit
The problem is that you have to convert your string _id to mongoose object id using this function mongoose.Types.ObjectId and my suggestion is to use findOne function instead of findById,
var mongoose = require('mongoose');
router.get('/experience/viewing/:viewexp_id', auth, async (req, res) => {
try {
let id = mongoose.Types.ObjectId(req.params.viewexp_id);
const exp = await Profile.findOne(
{ "experience._id": req.params.viewexp_id },
// This will show your sub record only and exclude parent _id
{ "experience.$": 1, "_id": 0 }
);
if (!exp) {
return res.status(404).json({ msg: 'Experience not found' });
}
res.json(exp);
} catch (err) {
console.error(err.message);
res.status(500).send(err.message);
}
});
var mongoose = require('mongoose');
router.get('/experience/viewing/:viewexp_id', auth, async (req, res) => {
try {
const exp = await Profile.findOne({
'experience._id': mongoose.Types.ObjectId(req.params.viewexp_id),
});
if (!exp) {
return res.status(404).json({ msg: 'Experience not found' });
}
res.json(exp);
} catch (err) {
console.error(err.message);
res.status(500).send(err.message);
}
});
You are saving object id . but your param id is string. convert it in ObjectId. Please check my solution.
router.post(
"/",
[
auth,
[
check("status", "status is required").not().isEmpty(),
check("skills", "skills is required").not().isEmpty(),
],
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const {
company,
website,
location,
bio,
status,
githubuername,
skills,
youtube,
facebook,
twitter,
instagram,
linkedin,
} = req.body;
const profileFileds = {};
profileFileds.user = req.user.id;
if (company) profileFileds.company = company;
if (website) profileFileds.website = website;
if (location) profileFileds.location = location;
if (bio) profileFileds.bio = bio;
if (status) profileFileds.status = status;
if (githubuername) profileFileds.githubuername = githubuername;
if (skills) {
profileFileds.skills = skills.split(",").map((skill) => skill.trim());
}
//Build profile object
profileFileds.social = {};
if (youtube) profileFileds.social.youtube = youtube;
if (twitter) profileFileds.social.twitter = twitter;
if (facebook) profileFileds.social.facebook = facebook;
if (linkedin) profileFileds.social.linkedin = linkedin;
if (instagram) profileFileds.social.instagram = instagram;
try {
let profile = await Profile.findOne({ user: req.user.id });
if (profile) {
//update
profile = await Profile.findOneAndUpdate(
{ user: req.user.id },
{ $set: profileFileds },
{ new: true }
);
return res.json(profile);
}
//Create profile
profile = new Profile(profileFileds);
await profile.save();
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send("server Error");
}
}
);

Mongoose: how to only populate, sort and return a nested object?

I have a User schema, with a messages array. The message array is filled by conversations id and referenced to a Conversation schema.
I want to fetch all conversations from a user, sort them by unread and then most recent messages. Finally, I must only return an array of lastMessage object.
For the moment, I have only managed to populate the whole user object.
Here is the Conversation Schema:
const conversationSchema = new mongoose.Schema(
{
name: { type: String, required: true, unique: true },
messages: [{ message: { type: String }, authorId: { type: String } }],
lastMessage: {
authorId: { type: String },
snippet: { type: String },
read: { type: Boolean },
},
},
{ timestamps: true }
);
conversationSchema.index({ name: 1 });
module.exports = mongoose.model("Conversation", conversationSchema);
And here is my code:
router.get("/conversations", async (req, res) => {
try {
const { userId } = req.query;
const user = await User.findById({ _id: userId }).populate("messages");
.sort({ updatedAt: 1, "lastMessage.read": 1 });
return res.json({ messages: user.messages });
} catch (err) {
console.log("error", err);
return res.json({ errorType: "unread-messages-list" });
}
});
How to do this?

Joining collections in mongodb with node js

I have two collections in mongodb "components" and "airframes" which I am trying to join together (with a one to many relationship). I have the following code which gets the airframe and component data separately from the database, however after days of effort, I cannot figure out how to join the two together. I assume I need to use $lookup to achieve the desired result but any assistance in constructing the code would be greatly appreciated.
my models are as follows and I am trying to join all the component records under the associated Airframe. the airframe field on the Component holds the related Airframes' id.
const airframeSchema = mongoose.Schema({
name: { type: String, required: true },
sNumber: { type: String, required: true },
aStatus: { type: String, required: true },
components: [ {
type: mongoose.Schema.Types.ObjectId,
ref: 'Component' } ]
});
module.exports = mongoose.model('Airframe', airframeSchema);
const componentSchema = mongoose.Schema({
name: { type: String, required: true },
serial: { type: String, required: true },
type: { type: String, required: true },
airFrame: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Airframe'},
});
module.exports = mongoose.model('Component', componentSchema);
the AirFramesService is as follow. I would like to join the component data under a array called "component".
getAirframes() {
this.http
.get<{ message: string; airframes: any }>("http://3.135.49.46:8080/api/airframes")
.pipe(
map(airframeData => {
return airframeData.airframes.map(airframe => {
return {
name: airframe.name,
sNumber: airframe.sNumber,
aStatus: airframe.aStatus,
id: airframe._id,
};
});
})
)
.subscribe(transformedAirframes => {
this.airframes = transformedAirframes;
this.airframesUpdated.next([...this.airframes]);
});
}
getAirframeUpdateListener() {
return this.airframesUpdated.asObservable();
}
getAirframe(id: string) {
return this.http.get<{ _id: string; name: string; sNumber: string ; aStatus: string}>(
"http://3.135.49.46:8080/api/airframes/" + id
);
}
The airframes route code is as follows:
router.get("", (req, res, next) => {
Airframe.find().then(documents => {
res.status(200).json({
message: "Airframes fetched successfully!",
airframes: documents
});
});
});
and here is the code within the ts component file that gets the airframe data is as follows.
constructor( public airframesService: AirframesService) {
this.airframesService.getAirframes();
this.airframesSub = this.airframesService.getAirframeUpdateListener()
.subscribe((airframes: Airframe[]) => {
this.isLoading = false;
this.airframes = airframes;
}, 0);
});
}
the desired outcome would be the following (at the moment I only get the airframe data):
{
_id: "123"
name: "Airframe01"
sNumber: "757"
aStatus: "Active"
id: "5e8052ad1fa18f1c73524664"
components: [
{
name: "Left Tank",
serial: "3456789",
type: "Landing Gear",
airFrame: "5e8052ad1fa18f1c73524664"
},
{
name: "Right Tank",
serial: "45678",
type: "Landing Gear",
airFrame: "5e8052ad1fa18f1c73524664"
}
]
}
Your document structure already established a one-to-many kinda relationship between the two models, you can use Mongoose population to get the join you described in the question. The populate code should be somewhere in the airframes route like this:
router.get("", (req, res, next) => {
// Notice the populate() method chain below
Airframe.find().populate('components').then(documents => {
// The documents output should have their "components" property populated
// with an array of components instead of just Object IDs.
res.status(200).json({
message: "Airframes fetched successfully!",
airframes: documents
});
});
});
You can read more about Mongoose population here.

TypeError: String cannot represent value: graphql Query not working

I am trying to run a graphql Query but it keeps giving me the "TypeError: String cannot represent value:" error.
The schema for my query:
type User {
active: Boolean!
email: String!
fullname: String!
description: String!
tags: [String!]!
}
type Query {
getAllUsers: [User]!
}
My resolver:
Query: {
getAllUsers: (_, __, { dataSources }) => {
return dataSources.userAPI.getAllUsers();
}
}
userAPI:
getAllUsers() {
const params = {
TableName: 'Users',
Select: 'ALL_ATTRIBUTES'
};
return new Promise((resolve, reject) => {
dynamodb.scan(params, function(err, data) {
if (err) {
console.log('Error: ', err);
reject(err);
} else {
console.log('Success');
resolve(data.Items);
}
});
});
}
The query:
query getAllUsers{
getAllUsers{
email
}
}
Since my email is a string, the error I'm getting is "String cannot represent value".
What's returned inside your resolver should match the shape specified by your schema. If your User schema is
type User {
active: Boolean!
email: String!
fullname: String!
description: String!
tags: [String!]!
}
then the array of Users you return should look like this:
[{
active: true,
email: 'kaisinnn#li.com',
fullname: 'Kaisin Li',
description: 'Test',
tags: ['SOME_TAG']
}]
The data you're actually returning is shaped much differently:
[{
active: {
BOOL: true
},
description: {
S: 'Test'
},
fullname: {
S: 'Kaisin Li'
},
email: {
S: 'kaisinnn#li.com'
},
}]
You need to either map over the array you're getting from the scan operation and transform the result into the correct shape, or write a resolver for each individual field. For example:
const resolvers = {
User: {
active: (user) => user.active.BOOL,
description: (user) => user.description.S,
// and so on
}
}

Accessing a schema inside a schema using Express Router and MongoDG

I'm trying to create a route where it takes in a parameter for a username and then displays that users information. Only thing is, the username is in the user schema from when the user signs up. The profile schema references the user schema. How do I use the username parameter in the findOne call to display the users profile data?
User schema:
const UserSchema = new Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
module.exports = User = mongoose.model("users", UserSchema);
Profile schema:
const ProfileSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "users"
},
name: {
type: String
},
image: {
type: String
},
bio: {
type: String
},
location: {
type: String
},
website: {
type: String
},
social: {
youtube: {
type: String
},
facebook: {
type: String
},
instagram: {
type: String
},
twitter: {
type: String
}
}
});
module.exports = User = mongoose.model("profile", ProfileSchema);
Route:
router.get("/user/:username", (req, res) => {
const errors = {};
Profile.findOne({ user: req.params.user.username })
.populate("user", "username")
.then(profile => {
if (!profile) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile);
})
.catch(err => res.status(404).json(err));
});
Please try this :
router.get("/user/:username", async (req, res) => {
const errors = {};
try {
const profile = await User.aggregate([
{ $match: { username: req.params.username } },
{ $lookup: { from: "profile", localField: "_id", foreignField: "user", as: "userProfile" } },
{ $project: { userProfile: { $arrayElemAt: ["$userProfile", 0] }, username: 1, _id:0 } }
]).exec();
if (!profile.length) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile[0]);
} catch (error) {
console.log('Error in retrieving user from DB ::', error);
return res.status(404);
}
})
Try using aggregate, firstly you check-in user table for getting details of a specific username then fetch the profile details as below using lookup, if no profile found after unwind the document will not be fetched and you can check on aggregate result's length as aggregate always return an array in result :
User.aggregate([
{$match:{ username: req.params.user.username }},
{$lookup:{from:"profile",localField:"_id",foreignField:"userId",as:"profileData"}},
{$unwind:"$profileData"},
{$project:{profileData:1,username:1}}
{$limit:1}
])
.then(profile => {
if (!profile.length) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile[0]);
})
You can do it in 2 steps.
Look for users containing username in userSchema, get it's id.
Then in promise, use that id to, look for profileSchema contains.
router.get("/user/:username", (req, res) => {
users.findOne({ username: req.params.username }).then(_user=>{
profile.findOne({ user: _user._id }).populate('user').then(_profile => {
res.json(_profile);
})
})
});
This code will look for username in userSchema and look for userSchema's id in profileSchema then returns profileSchema populated with user.

Resources