I'm new to feathersjs. Please help me understand these bits
So, I have this codes (products.services.js)
function mapUserIdToData(context) {
if (context.data && context.params.route.userId) {
context.data.user = context.params.route.userId;
}
}
app.use("/stores", new Stores(options, app));
const service = app.service("stores");
service.hooks(hooks);
// setup our nested routes
app.use("/users/:userId/stores", app.service("stores"));
app.service("users/:userId/stores").hooks({
before: {
find(context) {
if (context.params.route.userId)
context.params.query.user = context.params.route.userId;
},
create: mapUserIdToData,
update: mapUserIdToData,
patch: mapUserIdToData,
},
});
And in my register module (register.service.js), I called these logic to create store for new user
const users = this.app.service("users");
const user = await users.create(userData);
// save store
if (storeTitle) {
const stores = this.app.service("stores");
await stores.create({ user: user._id, title: storeTitle });
}
What I don't understand is : Why the line await stores.create({ user: user._id, title: storeTitle }); also trigger hooks logic in app.service("users/:userId/stores") ? I know because it run the mapUserIdToData function, which will be failed because there is no route param for userId.
Thank You
Related
How do I assign an 'owner' to content when it is created programatically with Strapi
I am trying to associate content created by a User (NOT an Admin User) to that User so that I can then restrict them to only that content later.
Apparentely this functionality doesn't exist out of the box but can be done through middleware, policy, or just custom controller logic.
I'm finding that I'm unable to assign it during the create step and having to do a second update call on every create. Is there a more eloquent way of doing this?
EDIT:
The first method is now working after updating the default User's Role, enabling:
User-Permissions > Auth > Connect
User-Permissions > User > Find
User-Permissions > User > Me
Though I'm still going to have to apply this to every method of every content's controller without a more streamlined solution.
'use strict';
/**
* store controller
*/
const { createCoreController } = require('#strapi/strapi').factories;
module.exports = createCoreController('api::store.store', ({ strapi }) => ({
async create(ctx) {
if (ctx?.state?.isAuthenticated) {
/**
* this does not work. The relationship is not assigned when I open the new store in the admin dashboard
* the userId is correctly added to the incoming data
*/
const userId = ctx.state.user.id
Object.assign(ctx.request.body.data, { users_permissions_user: userId })
console.log('ctx.request.body.data', ctx.request.body.data);
//ctx.request.body.data {
// name: 'User Created Store',
// description: 'this store was created by a logged in user',
// users_permissions_user: 74
//}
}
const response = await super.create(ctx)
console.log('response.data', response.data);
//response.data {
// id: 52,
// attributes: {
// name: 'User Created Store',
// description: 'this store was created by a logged in user',
// createdAt: '2022-07-07T15:48:16.362Z',
// updatedAt: '2022-07-07T15:48:16.362Z',
// publishedAt: '2022-07-07T15:48:16.360Z'
// }
//}
if (response?.data?.id) {
// this does work. The relationship is assigned and the correct user appears on the drop-down box
const userId = ctx.state.user.id
const updated = await strapi.entityService.update('api::store.store', response.data.id, { data: { users_permissions_user: userId } });
console.log('updated', updated);
//updated {
// id: 52,
// name: 'User Created Store',
// description: 'this store was created by a logged in user',
// createdAt: '2022-07-07T15:48:16.362Z',
// updatedAt: '2022-07-07T15:48:16.370Z',
// publishedAt: '2022-07-07T15:48:16.360Z'
//}
}
return response;
}
}));
I am storing a parking detail with a merchant id in the mongoose schema since a parking belongs to a certain merchant user and it cannot be empty or null.
Here is the model:
const parkingSchema = new mongoose.Schema({
merchantId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "Merchant",
},
//other details
})
merchant model is something like this:
const merchantSchema = new mongoose.Schema({
merchantId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Auth",
},
//other details
})
And finally the auth schema:
const authSchema = new mongoose.Schema({
accountType: {
type: String,
required: true,
trim: true,
default: "user",
enum: ["merchant", "user", "provider"],
},
//other details
})
If the original user wishes it, I simply want to update the parking data; otherwise, I want to throw an error.
I am using jsonwebtoken to authenticate users.
Here is the query to update the data:
exports.updateParking = async (req, res) => {
try {
const { parkingName, price, address, name, phoneNumber, about } = req.body;
const { parkingImage } = req.files;
const check_exist = await Auth.findById(req.data.id);
if (!check_exist) return res.status(404).json({ error: "User not found" });
console.log(req.data.id);
const updateData = await Parking.findByIdAndUpdate(
{ _id: req.params.id, merchantId: req.data.id }, // I think here is the problem
{
$set: {
parkingName,
price,
address,
...
},
}
);
return res.status(200).json({
success: true,
msg: "Parking has updated successfully",
});
} catch (error) {
return error.message;
}
};
However, the issue is that other users can now update another user's data which I want to stop
below is the query of middleware:
routing.patch("/parking/update/:id", middleware.authenticateToken, merchant.updateParking)
You should be showing each user only their parkings that they have created or belong to them.
const myParkings = async (req, res) => {
// always await code in try/catch block
const merchants = await Parkings.find({ user: req.user._id })
.. then populate the fields that you want to show
res.status(200).json({
success: true,
bookings,
});
};
you have to set this req.user._id when user logins. You could create a session.
I think what you're looking for is something like CASL Mongoose (or a similar package), and more specifically, the "conditions" section of the CASL docs.
What you're dealing with here is the distinction between 2 concepts:
AuthN (authentication) - determines who someone is and whether they are "authenticated" to make an API request
AuthZ (authorization) - determines what the authenticated user is allowed to do
In your app, middleware.authenticateToken is responsible for the AuthN piece of the equation. It makes sure that only users that have created an account are able to make requests to your API routes.
What you still need to solve for is the AuthZ piece, which can be done in a bunch of different ways, but one popular one is to use CASL, which is a Node AuthZ library that allows you to utilize your ORM's native query syntax to limit actions based on the authenticated (AuthN) user's attributes.
In other words, you can do something like, "Only allow user with ID 1 to update Parking entities that he/she owns". Below is generally what you're looking for (not tested for your use case, but the general idea is here):
const casl = require('#casl/ability');
// Define what a `Auth` (user) can do based on their database ID
function defineMerchantAbilities(merchantUser) {
const abilities = casl.defineAbility((allow, deny) => {
// Allow merchant to update a parking record that they own
allow('update', 'Parking', { merchantId: merchantUser.id })
})
return abilities
}
exports.updateParking = async (req, res) => {
const userId = req.data.id
const parkingId = req.params.id
// Find your merchant user in DB (see my comments at end of post)
const merchantUser = await Auth.findById(userId)
// Find your parking record
const parking = await Parking.findById(parkingId)
// Pass user to your ability function
const ability = defineMerchantAbilities(merchantUser)
// This will throw an error if a user who does not own this Parking record
// tries to update it
casl.ForbiddenError
.from(ability)
.throwUnlessCan('update', casl.subject('Parking', parking))
// If you make it here, you know this user is authorized to make the change
Parking.findByIdAndUpdate( ...your code here )
}
Additional comments/notes:
I would recommend removing your try/catch handler and using an Express default error handler as it will reduce the boilerplate you have to write for each route.
I would also recommend writing a middleware that finds a user by ID in the database and attaches it to a custom property called req.user so you always have req.user available to you in your authenticated routes.
I am making a small social media application in the MERN stack. I made an authSlice where I have registration, login, etc. and in the initial state I have a user object in which I place the user when logging in. I had a problem when I need to do a profile (follow / unfollow feature). I could do all this in authSlice where I have a user and manipulate it, but I would separate the profileSlice into another file. Now I'm wondering how to manipulate the user in authSlice? Because my user by model has the following: []; followers: []; when I fetch the following route I want to manipulate the user.following.
This is my following route:
export const followUser = async (req, res) => {
if (req.user.userId !== req.params.id) {
const user = await User.findById(req.params.id);
const currentUser = await User.findById(req.user.userId);
if (!user.followers.includes(req.user.userId)) {
await user.updateOne({ $push: { followers: req.user.userId } });
await currentUser.updateOne({ $push: { followings: req.params.id } });
res.status(200).json(req.user.userId);
} else {
res.status(403).json('you allready follow this user');
}
} else {
res.status(403).json('you cant follow yourself');
}
};
And in extrareducers when my request is fulfilled I need to change the user.following. Something like this:
user: {
...state.user,
followers: [...state.user.followers, state.userInfo._id],
},
I'm creating a backend for my React web application and I'm trying to subscribe a user to a match, this match is an object that have an array called "players" and when I click on the join button the username and profilePicture of the user are being dispatched to my backend. The first user info is sent perfectly but when a second user is subscribed the info of the first one is replaced for the second one.
This is my function that push the data:
const playerJoined = async (req, res) => {
const torneoId = req.params.id;
const uid = req.uid;
const profilePicture = req.profilePicture;
const username = req.username;
console.log(req.params);
try {
const torneo = await Torneo.findById(torneoId);
if (!torneo) {
return res.status(404).json({
ok: false,
msg: "Torneo no existe por ese ID",
});
}
const newPlayer = {
profilePicture: profilePicture,
username: username,
};
const nuevoTorneo = {
...req.body,
players: newPlayer,
};
const torneoActualizado = await Torneo.findByIdAndUpdate(
torneoId,
nuevoTorneo,
{
new: true,
}
);
res.json({
ok: true,
torneo: torneoActualizado,
});
} catch (error) {
console.log(error);
res.status(500).json({
ok: false,
msg: "Hable con el administrador",
});
}
};
My frontend is working well because when I added more users the array of objects shows all the players like this:
players: (2) [{…}, {…}]
But on my mongo DB shows only the last user info added like I mentioned before.
I really appreciate any help.
You seem to be replacing the players property instead of pushing into it.
const nuevoTorneo = {
...req.body,
players: newPlayer,
};
When you grab the torneo by id, you should have access to that players property already, so spread that array into your nuevoTorneo as well:
const nuevoTorneo = {
...req.body,
players: [...torneo.players, newPlayer],
};
It is because you always put your newPlayer into the "player" field of your nuevoTorneo and updated the same document. I assume you are using mongoose, You probably should just modify the "torneo" after your query and do something like this:
const torneo = await Torneo.findById(torneoId);
const newPlayer = {
profilePicture: profilePicture,
username: username,
};
torneo.nuevoTorneo.players.push(newPlayer);
await torneo.save();
Or to simply modify your code as:
const nuevoTorneo = {
...req.body,
players: [...torneo.nuevoTorneo.players,newPlayer],
};
I recommend the first method, let me know if you have any questions.
I am calling Stripe api every time the user creates a new product in Strapi, and that works perfectly.
What I would like to do is I would like to assign the Stripe ID from the response to a StripeId field inside Strapi after the response has arrived.
I tried this but it doesn't seem to be working:
module.exports = {
lifecycles: {
async afterCreate(result) {
const product = await stripe.products.create({
name: result.title,
});
result.StripeId = product.id;
},
},
};
Any idea how to do this?
Thanks!
The data needs to be altered before writing to DB, so this is the solution in case anyone needs to implement something similar to this:
module.exports = {
lifecycles: {
async beforeCreate(data) {
const product = await stripe.products.create({
name: data.title,
});
data.StripeId = product.id;
},
},
};
Cheers!