Error using sails.js and postgreSQL (using sails-postgresql module) - node.js

I want to ask a question. I'm developing an app using sails.js and PostgreSQL (using sails-postgresql module). I'm using UUID for my primary key type instead of integer. But there are some error when I try to insert a data to my database.
my model UserModel.js
var uuid = require('node-uuid');
module.exports = {
adapter: 'somePostgresqlServer',
autoPK: false,
migrate: 'safe',
attributes: {
ID: {
primaryKey: true,
type: 'string',
defaultsTo: function (){
return uuid.v4();
},
unique: true,
index: true,
uuidv4: true
},
username: {
type: 'string',
required: true,
unique: true
}
}
};
my create function in the controller
create: function(req, res) {
if (!req.param('_username') || !req.param('_newPassword') ||
!req.param('_confirmPassword') || !req.param('_emailAddress') ||
!req.param('_firstName') || !req.param('_lastName')) {
var errorMessage = ["All field are required to sign up"];
req.session.flash = {
err : errorMessage
}
res.redirect('/login');
return;
}
if (req.param('_newPassword') != req.param('_confirmPassword')) {
var errorMessage = ["New password and confirm password must be same"];
req.session.flash = {
err : errorMessage
}
res.redirect('/login');
return;
}
UserModel.create({
username: req.param('_username'),
encryptedPassword: req.param('_newPassword'),
emailAddress: req.param('_emailAddress'),
firstName: req.param('_firstName'),
lastName: req.param('_lastName')
}).exec(function(err,post) {
if (err) {
return res.error();
}
res.redirect('/');
})
res.redirect('/');
}
the error
/home/***/***/***/node_modules/sails-postgresql/lib/adapter.js:393
Object.keys(collection.schema).forEach(function(schemaKey) {
^
TypeError: Object.keys called on non-object
at Function.keys (native)
at __CREATE__ (/home/***/***/***/node_modules/sails-postgresql/lib/adapter.js:393:16)
at after (/home/***/***/***/node_modules/sails-postgresql/lib/adapter.js:1155:7)
at /home/***/***/***/node_modules/sails-postgresql/lib/adapter.js:1049:7
at /home/***/***/***/node_modules/sails-postgresql/node_modules/pg/lib/pool.js:77:9
at dispense (/***/***/***/node_modules/sails-postgresql/node_modules/pg/node_modules/generic-pool/lib/generic-pool.js:250:16)
at Object.me.acquire (/home/***/***/***/node_modules/sails-postgresql/node_modules/pg/node_modules/generic-pool/lib/generic-pool.js:319:5)
at Object.pool.connect (/home/***/***/***/node_modules/sails-postgresql/node_modules/pg/lib/pool.js:71:12)
at PG.connect (/home/***/***/***/node_modules/sails-postgresql/node_modules/pg/lib/index.js:49:8)
at spawnConnection (/home/***/***/***/node_modules/sails-postgresql/lib/adapter.js:1048:8)
at Object.module.exports.adapter.create (/home/***/***/***/node_modules/sails-postgresql/lib/adapter.js:361:7)
at module.exports.create (/usr/lib/node_modules/sails/node_modules/waterline/lib/waterline/adapter/dql.js:84:13)
at bound.createValues (/usr/lib/node_modules/sails/node_modules/waterline/lib/waterline/query/dql/create.js:214:16)
at /usr/lib/node_modules/sails/node_modules/waterline/lib/waterline/query/dql/create.js:74:20
at /usr/lib/node_modules/sails/node_modules/waterline/node_modules/async/lib/async.js:708:13
at /usr/lib/node_modules/sails/node_modules/waterline/node_modules/async/lib/async.js:49:16
I wish you can help me. Thanks for your attention :)

Try this:
Model
var uuid = require('node-uuid');
module.exports = {
adapter: 'somePostgresqlServer',
autoPK: false,
attributes: {
id: {
primaryKey : true,
type : 'string',
defaultsTo : function (){
return uuid.v4();
},
unique : true,
index : true
},
username: {
type : 'string',
required : true,
unique : true
}
}
};
Controller
create: function(req, res) {
var username = req.param('_username'),
newPassword = req.param('_newPassword'),
confirmPassword = req.param('_confirmPassword'),
emailAddress = req.param('_emailAddress'),
firstName = req.param('_firstName'),
lastName = req.param('_lastName');
if (!(username || newPassword || confirmPassword || emailAddress || firstName || lastName)) {
var errorMessage = ["All field are required to sign up"];
req.session.flash = {
err : errorMessage
}
return res.redirect('/login');
}
if (newPassword != confirmPassword) {
var errorMessage = ["New password and confirm password must be same"];
req.session.flash = {
err : errorMessage
}
return res.redirect('/login');
}
UserModel
.create({
username : username,
encryptedPassword : newPassword,
emailAddress : emailAddress,
firstName : firstName,
lastName : lastName
})
.then(function(post) {
res.redirect('/');
})
.catch(res.negotiate);
}
At models definition, there are no exist migrate, it's on model configuration.
uuidv4 also not a valid attribute.
id field is necessary while you use Blueprint API, look after blueprint hooks at actionUtil in your sails at node_modules.
Don't call res.redirect at the end of controller while you have async. process, it will give race condition when query not completed yet and you will get redirected.
Actually I don't pretty sure if it will solve your problem, but you can try it and give a result later.

Related

How to hide user form data in Request Payload

I am submitting a form by using "POST" method. But, even when I submit the form using "POST" method, I can see the submitted form data in http headers(Request Payload). I'm also using crypto to hash the password. This is the same login system I have deployed to heroku Simple Login System
Request Payload ScreenShot
This is my backend User Model
const mongoose = require('mongoose')
const crypto = require('crypto')
const userSchema = new mongoose.Schema({
username:{
type : String,
max : 32,
trim : true,
required : true,
unique : true,
index : true,
lowercase :true
},
name:{
type : String,
max : 32,
trim : true,
required : true
},
email:{
type : String,
trim : true,
required : true,
unique : true,
lowercase : true
},
profile:{
type : String,
required : true,
},
hashed_password:{
type : String,
required : true,
},
salt : String,
about :{
type : String
},
role :{
type: Number,
default : 0
},
photo:{
data : Buffer,
contentType : String
},
instagram:{
type: String
},
resetPasswordLink : {
data : String,
default : ''
}
},{timestamp : true})
userSchema.virtual('password')
.set(function(password){
//create a temporary variable called _password
this._password = password;
//generate salt
this.salt = this.makeSalt()
//encrypt password
this.hashed_password = this.encryptPassword(password)
})
.get(function(){
return this._password;
})
userSchema.methods = {
authenticate: function(plainText){
return this.encryptPassword(plainText) === this.hashed_password;
},
encryptPassword :function(password){
if(!password) return ''
try{
return crypto.createHmac('sha1',this.salt)
.update(password)
.digest('hex')
}
catch(err) {
return ''
}
},
makeSalt : function(){
return Math.round(new Date().valueOf * Math.random() + '');
}
}
module.exports = mongoose.model('User',userSchema);
Controller Method
const shortId = require('shortid')
const jwt = require('jsonwebtoken')
const expressJwt = require('express-jwt')
exports.signup = (req,res) => {
User.findOne({email: req.body.email}).exec((err,user)=>{
if(user) {
return res.status(400)
.json({ error : 'Email already exist'})
}
const { name, email, password } = req.body
let username = shortId.generate()
let profile = `${process.env.CLIENT_URL}/profile/${username}`
let newUser = new User( { name, email , password, profile, username })
newUser.save((err,success)=>{
if(err){
return res.status(400)
.json({error: err})
}
// res.json({
// user:success
// })
res.json({ message: 'Signup Success ! Please return to the Login page' })
})
})
};
exports.signin = (req,res) =>{
const { email,password } = req.body;
// check if user exists
User.findOne({ email }).exec((err,user)=> {
if(err || !user ){
return res.status(400).json({
error : 'User with that email does not exist,Please Sign Up'
});
}
// user authentication
if(!user.authenticate(password)) {
return res.status(400).json({
error : 'Email and password do not match'
});
}
// generate a token and send to client
const token = jwt.sign({ _id: user._id},process.env.JWT_SECRET, {expiresIn : '1d'} )
res.cookie('token',token, { expiresIn:'2d' })
const { _id, username, name, email, role } = user;
return res.json({
token,
user : { _id, username, name, email, role }
})
});
}
exports.signout = (req,res)=> {
res.clearCookie("token")
res.json({ message: 'Successfully Signed Out' })
}

E11000 duplicate key error index: mongo.users.$password_1 dup key:

I had this error even though i have sparse index set to true on that feild password
here is user schema:
var roledef = 'Member';
var mongoose = require('mongoose');
var UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
sparse: true,
minlength: [5, 'Username must be grater than 5 characters']
},
password: {
type: String,
required: true,
sparse: true,
minlength: [5, 'Username must be grater than 5 characters']
},
email: {
type: String,
required: true
},
profileid: {
type: String,
required: true,
},
roles: {
type: String,
default: roledef,
sparse: true
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
// write encryption
and i am routing throug a index file which works
but i dont know if this is the best way to create a user
here is my user controller file :
const userModel = require('../../models');
const UserController = {};
const User = require('../../models/User')
const sha256 = require('sha256');
module.exports.validateregistration = (req, res) => {
console.log("##########################################################".red);
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
if (req.body.encodedUsername && req.body.encodedPassword && req.body.encodedEmail) {
// res.send(result);
log(ip);
var buffU = new Buffer(req.body.encodedUsername, 'base64');
var buffP = new Buffer(req.body.encodedPassword, 'base64');
var buffE = new Buffer(req.body.encodedEmail, 'base64');
var Username = buffU.toString('ascii');
var Pass = buffP.toString('ascii');
var Email = buffE.toString('ascii');
var hashPass = sha256.x2(Pass);
console.log("Register request for " + "Username: " + Username);
const userData = {
username: Username,
password: hashPass,
email: Email,
profileid: sha256(buffU)
};
const user = new userModel.User({
username: Username,
password: hashPass,
email: Email,
profileid: sha256(buffU)
});
const details = userData;
user.save().then((newuser) => {
res.status(200).json({
success: true,
data: newuser
});
}).catch((err) => {
res.status(500).json({
message: err
});
console.log(err);
});
// User.findOne({ username: userData.username, email: userData.email }, (err, resultofcheck) => {
// console.log(resultofcheck)
// if (resultofcheck == null) {
// User.create(userData, function (err, user) {
// if (err) {
// console.log(err);
// res.send("2")
// } else {
// console.log(user);
// console.log("New user created: " + user.username + " Profile id: " + user.profileid);
// res.redirect("/api/profile/" + user.profileid);
// }
// });
// } else {
// console.log(Username + " is taken")
// console.log(err);
// res.send("2");
// };
// });
}
console.log("##########################################################".red);
};
// if (!result) {
// db.collection('users').insert(details, (err, resultofinsert) => {
// if (err) {
// console.log(err);
// res.sendStatus(500);
// } else {
// res.send(user.profileid);
// console.log("New user created: " + user);
// }
// });
// };
// db.collection('users').insert(user, (err, result) => {
// if (err) {
// res.sendStatus(501);
// res.send({ 'error': 'An error has occured!' });
// } else {
// // res.send(result.ops[0].pass);
// console.log(result);
// };
// });
im calling throug another index file but iworks also but i get this error:
{ MongoError: E11000 duplicate key error index:
mongo.users.$password_1 dup key: { :
"e701ea082879498082d025e0cf9857ec0d19e6e86fa39f92ed3286de55d340e6" }
at Function.create (C:\Users\abinash\Desktop\api\node_modules\mongodb-core\lib\error.js:43:12)
at toError (C:\Users\abinash\Desktop\api\node_modules\mongoose\node_modules\mongodb\lib\utils.js:149:22)
at coll.s.topology.insert (C:\Users\abinash\Desktop\api\node_modules\mongoose\node_modules\mongodb\lib\operations\collection_ops.js:828:39)
at C:\Users\abinash\Desktop\api\node_modules\mongodb-core\lib\connection\pool.js:532:18
at _combinedTickCallback (internal/process/next_tick.js:131:7)
at process._tickCallback (internal/process/next_tick.js:180:9) driver: true, name: 'MongoError', index: 0, code: 11000,
errmsg: 'E11000 duplicate key error index: mongo.users.$password_1 dup
key: { :
"e701ea082879498082d025e0cf9857ec0d19e6e86fa39f92ed3286de55d340e6" }',
[Symbol(mongoErrorContextSymbol)]: {} }
and even though the username and email feilds are different and password isnt required to be unique i tryed to use sparse: true but it dont work pls hep point out what i did wrong
the commented outt parts are the things itryed before i found out about mongoose models

Bcrypt error in simple Node router

I have a route for creating users in Node/Express. I am getting a weird error about a method on the model not existing.
Here is the model for users:
'use strict';
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
mongoose.Promsie = global.Promise;
const UserSchema = mongoose.Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: { type: String, required: true },
firstName: { type: String },
lastName: { type: String },
families: [
{
family_key: { type: String, required: true },
family_name: { type: String }
}
]
});
UserSchema.methods.apiRepr = function() {
return {
id: this._id,
firstName: this.firstName,
lastName: this.lastName,
username: this.username,
email: this.email,
families: this.families
};
};
UserSchema.methods.hashPassword = function(password) {
return bcrypt.hash(password, 10);
}
UserSchema.methods.validatePassword = function(password) {
return bcrypt.compare(password, this.password);
}
const User = mongoose.models.User || mongoose.model('User', UserSchema);
module.exports = { User };
Not particularly complicated. BUT, my the route is having trouble with the "hashPassword" method. When I try to use this route, I get an error that says "TypeError: User.hashPassword is not a function"
Here is the route (the issue is close to the bottom):
router.post('/', jsonParser, (req, res) => {
// checking that required fields are present
const requiredFields = ['username', 'password', 'email'];
const missingField = requiredFields.find(field => !(field in req.body));
if(missingField) {
return res.status(422).json({
code: 422,
reason: 'Validation Error',
message: 'Missing field',
location: missingField
});
}
// checking the format of string fields
const stringFields = ['username', 'password', 'email', 'lastname', 'firstname'];
const nonStringField = stringFields.find(
field => field in req.body && typeof req.body[field] !== 'string'
);
if (nonStringField) {
return res.status(422).json({
code: 422,
reason: 'Validation Error',
message: 'Incorrect field type: expected string',
location: nonStringField
});
}
// checking the trimming on fields
const trimmedFields = ['username', 'password', 'email'];
const nonTrimmedField = trimmedFields.find(
field => req.body[field].trim() !== req.body[field]
);
if (nonTrimmedField) {
return res.status(422).json({
code: 422,
reason: 'Validation Error',
message: 'Cannot start or end with whitespace',
location: nonTrimmedField
});
}
// checking length of fields with required length
const sizedFields = {
username: { min: 1 },
password: { min: 10, max: 72 }
};
const tooSmallField = Object.keys(sizedFields).find(field =>
'min' in sizedFields[field] &&
req.body[field].trim().length < sizedFields[field].min
);
const tooLargeField = Object.keys(sizedFields).find(field =>
'max' in sizedFields[field] &&
req.body[field].trim().length > sizedFields[field].max
);
if (tooSmallField || tooLargeField) {
return res.status(422).json({
code: 422,
reason: 'Validation Error',
message: tooSmallField
? `Must be at least ${sizedFields[tooSmallField].min} characters long`
: `Must be at most ${sizedFields[tooLargeField].max} characters long`,
location: tooSmallField || tooLargeField
});
}
// creating the user
let { username, firstname, lastname, families, email, password } = req.body;
return User.find({ username })
.count()
.then(count => {
if(count > 0) {
return Promise.reject({
code: 422,
reason: 'Validation Error',
message: 'Username already taken',
location: 'username'
});
}
return User.hashPassword(password);
})
.then(hash => {
return User.create({ username, firstname, lastname, families, email, password: hash })
})
.then(user => {
return res.status(201).json(user.apiRepr());
})
.catch(err => {
console.error(err)
res.status(500).json({ code: 500, message: 'Internal server error'})
})
})
It does not like the return User.hashPassword(password) part. Any thoughts about what is causing this? I'm copying from a working app. Not sure what I'm doing wrong here.
The methods in node.js can not be used directly using the SchemaName you need to create an object of the schema name and then use the methods of the schema.
Ex:
var AnimalSchema = new Schema({
name: String
, type: String
});
AnimalSchema.methods.findSimilarType = function findSimilarType (cb) {
return this.model('Animal').find({ type: this.type }, cb);
};
var Animal = mongoose.model('Animal', AnimalSchema);
var dog = new Animal({ name: 'Rover', type: 'dog' });
dog.findSimilarType(function (err, dogs) {
if (err) return ...
dogs.forEach(..);
})
Source: http://mongoosejs.com/docs/2.7.x/docs/methods-statics.html
In your code you are trying to access the methods from the model.
Instantiate the model then use the methods.
If need use like the way you are using in the code try using function instead of methods.
module.exports.funtionName = function(/*function params*/){
//function body here
};

Mongoose populate() returns empty array

I want to query the subdocument array by the property 'token' in clientSchema. But I'm not able to populate the subdocument array. It always returns empty value.
This is what I'm tried
var performAuthAsync = promise.promisify(performAuth);
var response = {};
performAuthAsync(req).then(function (client) {
sendStatus(res, 200, { "success": "true", "value": client });
}).catch(ApiError, function (e) {
response.error = "true";
response.message = e.message;
if (e.message == "Invalid Authorization" || e.message == "Unauthorized access") {
console.log(e.message);
sendStatus(res, 401, response, req.query.type);
}
else {
sendStatus(res, 500, response, req.query.type);
}
});
PerformAuth method
function performAuth(req, callback) {
try {
var authHeader = req.headers.authorization;
console.log(authHeader);
//error in req format
if (!authHeader || !authHeader.startsWith("Basic ")) {
console.log("inside fail authheader");
return callback(new ApiError("Invalid Authorization"));
}
authHeader = authHeader.replace("Basic ", "");
authHeader = Buffer.from(authHeader, 'base64').toString('ascii');
console.log(authHeader);
//temporary populate check
clientApp.findOne({}).populate({
path: 'appClients',
model: 'TClient'
}).exec(function (error, apps) {
console.log("populated apps check " + apps); //object containing empty array
//{ _id: 5987099f2cb916a0de80f067,
// appSecret: 'THisIsSecret',
// appId: 'W5ikGw16dQjgWm8bGjqdAwi1IDR2XibD3XESYokH',
// appClients: [] }
// mongo console output
// { "_id" : ObjectId ("5987099f2cb916a0de80f067"),
// "appSecret" : "THisIsSecret",
// "appId" : "W5ikGw16dQjgWm8bGjqdAwi1IDR2XibD3XESYokH",
// "appClients" : [ ObjectId("59881a64dbab536016e7f970") ], "__v" : 0 }
});
clientApp.findOne({}).populate('appClients').findOne({
'appClients.token': authHeader
}).exec(function (error, client) {
if (error) {
console.log("inside dberror");
console.error(error);
return callback(error, null);
}
if (!client) {
return callback(new ApiError("Unauthorized access"), null);
}
return callback(client);
});
}
catch (exception) {
console.log("inside exception");
console.error(exception);
return callback(exception, null);
}
}
Clientapp and client schemas: ( they are in different files)
var appSchema = new Schema({
appId: {
type: String,
required: true,
unique: true
},
appSecret: {
type: String,
required: true,
unique: true
},
appClients: [{ type: Schema.Types.ObjectId, ref: 'TClient' }],
createdAt: Date,
modifiedAt: Date
});
// model
var clientApp = mongoose.model('ClientApp', appSchema);
var clientSchema = new Schema({
clientId: {
type: String,
required: true,
unique: true
},
info: {
type: String,
required: true,
},
token: {
type: String,
required: true,
unique: true
},
createdAt: Date,
modifiedAt: Date
});
// model
var tclient = mongoose.model('TClient', clientSchema);
What I'm doing wrong? Any help is appreciated
The findOne() function return only one document. After that the populate method populates the sub document value. But the next findOne() doesn't work, so you get a null response.
You could try this instead:
async function performAuth(){
let authHeader = authHeader.replace("Basic ", "");
authHeader = Buffer.from(authHeader, 'base64').toString('ascii');
const clientApp = await clientApp.find({})
.populate('appClients')
.map(data => data.find(item=>item.appClients.token === authHeader).exec();
}
Operations Performed
Get all the clientApps
Populate the appClients
Find the clientApp whose token matches the authHeader

How to update user's profile using mongo, passport and node js?

I'm creating a personal project. I choose Passport.js for authentifications and I would like to update user's profile using this mongoose schema :
var userSchema = mongoose.Schema({
local : {
email : {type: String, required: true, unique: true},
password : {type: String, required: true}
},
first_name : {type: String, required: true},
last_name : {type: String, required: true},
username : {type: String, required: true, unique: true},
email : {type: String, required: true, unique: true}});
Here, the post route that I use :
app.post('/editProfile', isLoggedIn, function(req, res, next){
User.update({ _id: req.user.id}, req.body, function(err, user){
if(!user){
req.flash('error', 'No account found');
return res.redirect('/edit');
}
var emailEdit = req.body.email;
var usernameEdit = req.body.username;
var first_nameEdit = req.body.firstname;
var last_nameEdit = req.body.lastname;
if(emailEdit.lenght <= 0 || usernameEdit.lenght <= 0 || first_nameEdit.lenght <= 0 || last_nameEdit.lenght <= 0){
req.flash('error', 'One or more fields are empty');
res.redirect('/edit');
}
else{
user.email = emailEdit;
user.local.email = emailEdit;
user.first_name = first_nameEdit;
user.last_name = last_nameEdit;
user.username = usernameEdit;
res.redirect('/profile/');
}
});
When I run it, I have an error and I don't understand why
TypeError: Cannot set property 'email' of undefined
because of user.local.email = emailEdit; When I comment this line, only username is updated.
I'm sure it's a stupid mistake that I did but I can't find it.
I'm also looking for an eventual much more efficient way to update profile using passport, node and mongo. And if possible a dynamic one which, for example, I could check in realtime if username is not already taken and set the field in red in this case.
Model.update's callback does not return a document.
In your case, I would just use findById and save.
app.post('/editProfile', isLoggedIn, function(req, res, next){
User.findById(req.user.id, function (err, user) {
// todo: don't forget to handle err
if (!user) {
req.flash('error', 'No account found');
return res.redirect('/edit');
}
// good idea to trim
var email = req.body.email.trim();
var username = req.body.username.trim();
var firstname = req.body.firstname.trim();
var lastname = req.body.lastname.trim();
// validate
if (!email || !username || !firstname || !lastname) { // simplified: '' is a falsey
req.flash('error', 'One or more fields are empty');
return res.redirect('/edit'); // modified
}
// no need for else since you are returning early ^
user.email = email;
user.local.email = email; // why do you have two? oh well
user.first_name = firstname;
user.last_name = lastname;
user.username = username;
// don't forget to save!
user.save(function (err) {
// todo: don't forget to handle err
res.redirect('/profile/');
});
});
});
const updateUser = (req ,res, next)=>{
const updateData = req.body.update;
if (!updateData){
res.status(422).send({"message":"please provide what you want to update"})
}
User.findOne({email:req.body.user.email}).then(function(user) {
if (!user) { return res.sendStatus(401); }
//NOTE only update fields that were actually passed...
if (typeof updateData.username !== 'undefined') {
user.username = updateData.username;
}
if (typeof updateData.email !== 'undefined') {
user.email = updateData.email;
}
if (typeof updateData.first_name !== 'undefined') {
user.email = updateData.email;
}
if (typeof updateData.last_name !== 'undefined') {
user.email = updateData.email;
}
if (typeof updateData.bio !== 'undefined') {
user.bio = updateData.bio;
}
if (typeof updateData.image !== 'undefined') {
user.image = updateData.image;
}
if (typeof updateData.password !== 'undefined') {
user.setPassword(updateData.password);
}
return user.save()
.then(function() {
return res.json({ user: user.toAuthJSON() });
});
}).catch(()=>{
res.status(422).send({"message":"couldn't update user"})
}
);
};
UserSchema.methods.generateJWT = function() {
var today = new Date();
var exp = new Date(today);
exp.setDate(today.getDate() + 60);
return jwt.sign({
id: this._id,
username: this.username,
exp: parseInt(exp.getTime() / 1000),
}, config.secret);
};
UserSchema.methods.toAuthJSON = function() {
return {
username: this.username,
email: this.email,
token: this.generateJWT(),
bio: this.bio,
avatar: this.image
};
};

Resources