Strapi update username from custom controller - strapi

I am trying to create a custom controller to update the user profile.
I created the routing file and the corresponding controller.
Routing file: server/src/api/profile/routes/profile.js
module.exports = {
routes: [
{
method: 'GET',
path: '/profile',
handler: 'profile.getProfile',
},
{
method: 'PUT',
path: '/profile',
handler: 'profile.updateProfile',
},
]
}
Controller: src/api/profile/controllers/profile.js
async updateProfile(ctx) {
try {
const { id } = ctx.state?.user;
const user = strapi.query('admin::user').update({
where: { id },
data: {
username: "testUsername"
}
})
ctx.body = "User updated"
} catch(error) {
ctx.badRequest("Something went wrong", { error })
}
},
The above code returns "User updated", but the username does not update. I am executing the PUT call with a correct Bearer authorisation token and the user permissions for that user are set to enable "updateProfile".
Oddly enough, the same code, when changed to update a different API item, works perfectly fine:
async updateArticle(ctx) {
try {
const { id } = ctx.state?.user;
const article = strapi.query('api::article.article').update({
where: { author: id },
data: {
title: "New title"
}
})
ctx.body = article
} catch(error) {
ctx.badRequest("Something went wrong", { error })
}
},
I am also confused by different syntaxes appearing in the official Strapi documentation, for example some docs mention:
strapi.query('admin::user').update({ id }, data)
But in other places in the documentation its:
strapi.plugins['users-permissions'].services.user.update({ id });
And then elsewhere:
strapi.query('user', 'users-permissions').update(params, values);
Another question is: do I need to sanitise the input / output in any way? If yes, how? Importing sanitizeEntity from "Strapi-utils" doesn't work, but it's mentioned in several places on the internet.
Additionally, I cannot find a list of all ctx properties. Where can I read what is the difference between ctx.body and ctx.send?
The lack of good documentation is really hindering my development. Any help with this will be greatly appreciated.

Related

how to create custom endpoint in strapi v4

I've migrated from strapi v3 to strapi v4 and i wants to create custom endpoint 'events/me' by this code in "src/api/event/controllers" directory:
"use strict";
const { sanitizeEntity } = require("strapi-utils");
module.exports = {
// Get logged in users
async me(ctx) {
const user = ctx.state.user;
if (!user) {
return ctx.badRequest(null, [
{ messages: [{ id: "No authorization header was found" }] },
]);
}
const data = await strapi.services.events.find({ user: user.id });
if (!data) {
return ctx.notFound();
}
return sanitizeEntity(data, { model: strapi.models.events });
},
};
but i encountered to this error:
Cannot find module 'strapi-utils'
it seems that "strapi-utils" there isn't in strapi v4.
by addition, there isn't any "cofing/routes.json" file. this has been replaced by "route/event.js" file.
strapi document couldn't help me. Any help would be appreciated
You could import it from #strapi/utils.
The sanitize function of Strapi has a bit changed in v4: you have to use the Content API and provide the schema for the sanitized object.
Here you can find an example of use: https://github.com/strapi/strapi/blob/main/packages/plugins/users-permissions/server/controllers/user.js

strapi - restrict user to fetch only data related to him

Usually, a logged-in user gets all entries of a Content Type.
I created a "snippets" content type (_id,name,content,users<<->>snippets)
<<->> means "has and belongs to many" relation.
I created some test users and make a request:
curl -H 'Authorization: Bearer eyJ...' http://localhost:1337/snippets/
Main Problem: an authenticated user should only see the entries assigned to him. Instead, a logged-in user gets all snippets, which is bad.
How is it possible to modify the fetchAll(ctx.query); query to take that into account so it does something like fetchAll(ctx.state.user.id); at the /-route->find-method ?
The basic find method is here:
find: async (ctx) => {
if (ctx.query._q) {
return strapi.services.snippet.search(ctx.query);
} else {
return strapi.services.snippet.fetchAll(ctx.query);
}
},
Sub-Question: Does strapi even know which user is logged in when I do Bearer-Token Authentication ?
You could set up a /snippets/me route under the snippets config.
That route could call the Snippets.me controller method which would check for the user then query snippets based on the user.
So in api/snippet/config/routes.json there would be something like :
{
"method": "GET",
"path": "/snippets/me",
"handler": "Snippets.me",
"config": {
"policies": []
}
},
Then in the controller (api/snippet/controllers/Snippet.js), you could do something like:
me: async (ctx) => {
const user = ctx.state.user;
if (!user) {
return ctx.badRequest(null, [{ messages: [{ id: 'No authorization header was found' }] }]);
}
const data = await strapi.services.snippet.fetch({user:user.id});
if(!data){
return ctx.notFound();
}
ctx.send(data);
},
Then you would give authenticated users permissions for the me route not for the overall snippets route.
The above is correct, except with newer versions of strapi. Use find and not fetch :)
const data = await strapi.services.snippet.find({ user: user.id });
Strapi v3.0.0-beta.20
A possibility would be to extend the query used by find and findOne in the controllers with a restriction regarding the logged in user. In this case you might also want to adapt the count endpoint to be consistent.
This would result in:
withOwnerQuery: (ctx, ownerPath) => {
const user = ctx.state.user;
if (!user) {
ctx.badRequest(null, [
{ messages: [{ id: "No authorization header was found" }] },
]);
return null;
}
return { ...ctx.query, [ownerPath]: user.id };
};
find: async (ctx) => {
ctx.query = withOwnerQuery(ctx, "owner.id");
if (ctx.query._q) {
return strapi.services.snippet.search(ctx.query);
} else {
return strapi.services.snippet.fetchAll(ctx.query);
}
},
// analogous for for findOne
Depending on your usage of controllers and services you could achieve the same thing via adapting the service methods.
This kind of solution would work with the GraphQL plugin.

Writing Structural Expectations with Jest

I am looking to write what I am calling structural expectations with Jest and I am not sure how this could be accomplished.
To start I have a graphql server and a database with a number of todo items. I currently have the following test that just returns true if the content within the database is the same as the response that I have written. I want to check instead that the response looks like an object with data that could be anything.
Here is the code that I have:
describe('To Do:', () => {
it('add todo items', async () => {
const response = await axios.post('http://localhost:5000/graphql', {
query: `
query {
getTodoItems {
message
id
dateCreated
dateDue
}
}
`
});
const { data } = response;
expect(data).toMatchObject({
data: {
getTodoItems: [
{
message: "message",
id: "5bd9aec8406e0a2170e04494",
dateCreated: "1540992712052",
dateDue: "1111111111"
},
{
message: "message",
id: "5bd9aeec60a9b2579882a308",
dateCreated: "1540992748028",
dateDue: "1111111111"
},
{
message: "new message",
id: "5bd9af15922b27236c91837c",
dateCreated: "1540992789836",
dateDue: "1111111111"
}
]
}
})
});
});
Now I want to write something like this, where there can be any number of returned items and they follow similar structuring:
describe('To Do:', () => {
it('add todo items', async () => {
const response = await axios.post('http://localhost:5000/graphql', {
query: `
query {
getTodoItems {
message
id
dateCreated
dateDue
}
}
`
});
const { data } = response;
expect(data).toMatchObject({
data: {
getTodoItems: [
{
message: expect.any(String),
id: expect.any(String),
dateCreated: expect.any(String),
dateDue: expect.any(String)
} // There needs to be unlimited additional items here
]
}
})
});
});
I have been looking throught the docs and I even tried nesting the expectations but I can't seem to get the desired response. Let me know what yo think or if I can clarify in any way.
I figured out the best way for me to do it. I would love to hear better answers. I wrote a function within the scope of the test as a jest.fn and then I called it. In that function, I made custom checks to parse the data that was received in the response. From there I added an expect function with the 'toHaveReturnedWith' method to see what the response of my custom function was and finishing out the test.
const addTodoResponse = jest.fn(() => {
// Custom parsing and check here
// Returns true or false
});
addTodoResponse();
expect(addTodoResponse).toHaveReturnedWith(true);
Are there better ways to do this out there?

How to stub a call to graphql using cypress?

I'm writing a Vue app that uses vue-apollo to interact with graphql. I'm wondering if it's possible to stub the graphql requests. I thought this should work:
it('should access a story', function() {
cy.server();
cy.route('http://localhost:3002/graphql', {
data: {
Story: { id: 2, title: 'story title', content: 'story content' }
}
});
cy.visit('/stories/2');
});
Unfortunately, I get an error from graphql complaining that id is an Int instead of an ObjectId. Am I missing something?
The problem was that stubbing fetch requests isn't yet implemented in Cypress (which is what Vue Apollo is using). I ended up following these instructions:
Install github/fetch
Add this to cypress/support/index.js:
.
Cypress.on('window:before:load', win => {
win.fetch = null;
win.Blob = null;
});
Now it works!
I got it working with this package here:
npm i #iam4x/cypress-graphql-mock
Add this line to 'support/commands.js'
import "#iam4x/cypress-graphql-mock";
go to your graphiql playground and download your schema
add task command to 'plugins/index.js' (REMEMBER TO CHANGE PATH TO SCHEMA FILE YOU DOWNLOADED EARLIER)
module.exports = (on, config) => {
on("task", {
getSchema() {
return fs.readFileSync(
path.resolve(__dirname, "../../../schema.graphql"),
"utf8"
);
}
});
};
write your tests with loaded schema
beforeEach(() => {
cy.server();
cy.task("getSchema").then(schema => {
cy.mockGraphql({
schema
});
});
});`
describe("Login Form", () => {
it("should redirect after login", () => {
cy.mockGraphqlOps({
operations: {
Login: {
login: {
jwt: "some-token",
user: {
id: "5d5a8e1e635a8b6694dd7cb0"
}
}
}
}
});
cy.visit("/login");
cy.getTestEl("email-input").type("Max Mustermann");
cy.getTestEl("password-input").type("passwort");
cy.getTestEl("submit").click();
cy.getTestEl("toolbar-title").should("exist");
});
})
Visit the original repo for further explanation as i find it less confusing. The package you have installed is just a working fork of this one:
https://github.com/tgriesser/cypress-graphql-mock

Relayjs Graphql user authentication

Is it possible to authenticate users with different roles solely trough a graphql server in combination with relay & react?
I looked around, and couldn't find much info about this topic.
In my current setup, the login features with different roles, are still going trough a traditional REST API... ('secured' with json web tokens).
I did it in one of my app, basically you just need a User Interface, this one return null on the first root query if nobody is logged in, and you can then update it with a login mutation passing in the credentials.
The main problem is to get cookies or session inside the post relay request since it does'nt handle the cookie field in the request.
Here is my client mutation:
export default class LoginMutation extends Relay.Mutation {
static fragments = {
user: () => Relay.QL`
fragment on User {
id,
mail
}
`,
};
getMutation() {
return Relay.QL`mutation{Login}`;
}
getVariables() {
return {
mail: this.props.credentials.pseudo,
password: this.props.credentials.password,
};
}
getConfigs() {
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {
user: this.props.user.id,
}
}];
}
getOptimisticResponse() {
return {
mail: this.props.credentials.pseudo,
};
}
getFatQuery() {
return Relay.QL`
fragment on LoginPayload {
user {
userID,
mail
}
}
`;
}
}
and here is my schema side mutation
var LoginMutation = mutationWithClientMutationId({
name: 'Login',
inputFields: {
mail: {
type: new GraphQLNonNull(GraphQLString)
},
password: {
type: new GraphQLNonNull(GraphQLString)
}
},
outputFields: {
user: {
type: GraphQLUser,
resolve: (newUser) => newUser
}
},
mutateAndGetPayload: (credentials, {
rootValue
}) => co(function*() {
var newUser = yield getUserByCredentials(credentials, rootValue);
console.log('schema:loginmutation');
delete newUser.id;
return newUser;
})
});
to keep my users logged through page refresh I send my own request and fill it with a cookie field... This is for now the only way to make it work...

Resources