I have created a books content type containg books. Each book in the collection belongs to a user (user content type provided by Strapi).
I want to return list of books owned by authenticated user at /users/me/books endpoint. Where can I add this route and handler as there is /api/books directory containing books related route, controllers, etc. but not /api/users directory.
You can extend or override using the extensions system.
extensions/users-permissions/controllers
Just add the controller you want to extend or override as a .js file like so:
So to override the me endpoint under User.js you only need to define the method again:
'use strict';
module.exports = {
//Override me
async me(ctx) {
//do your thing
}
};
To extend, not override, means to add another endpoint, therefor you need to define it, add a route and set permissions for it. The routes.js files should be created at:
extensions/users-permissions/config/routes.json
Like so:
{
"routes": [
{
"method": "GET",
"path": "/users/me/books",
"handler": "User.getUserBooks",
"config": {
"policies": [],
"prefix": "",
"description": "description",
"tag": {
"plugin": "users-permissions",
"name": "User",
"actionType": "find"
}
}
}
}
The controller this time (same location as in beginning):
module.exports = {
async getUserBooks(ctx) {
//add logic
}
}
OP correctly added:
After adding custom route and controller, one has to go to Admin Panel(log in as admin)>Roles and Permission> Users-Permission. There you can find the newly added route and have to enable it by checking it.
The originals(if you need examples) are located at:
/node_modules/strapi-plugin-users-permissions/config/routes.json
/node_modules/strapi-plugin-users-permissions/controllers/User.js
I don't think you should extend the User controller as it isn't logically correct. You are trying to GET books - you should extend the book api in the same way.
From what I can tell a ContentType doesn't contain information about its creator(you're welcome to educate me if it's not true).
So to tackle that you can add to your ContentType "books" a relation to User.
Then I think you should extend the books api with a endpoint that returns books "belonging" to that user using the ctx received.
Also - check this question out
Comment if you need more info.
Related
I'm trying to setup the allow list feature in Hasura, but the docs seem pretty sparse. This is one of the queries:
{
hasura_auth(args: {cleartext_password: "xxx", email: "email#mail.com"}) {
jwt_token
}
}
How would I integrate the dynamic parts in an allow list?
I tried this and lot's of variations with no luck:
{
hasura_auth(args: {cleartext_password: $pass, email: $email}) {
jwt_token
}
}
Thanks for your help!
What you have to know is to tell hasura the name of your query with full syntax.
like this...
Operation name is => get_user_by_pk
Operation is
query get_user_by_pk($id: uuid!) {
user_by_pk(id: $id) {
id
username
email
}
}
the main part is you have to use the exact operation in your code having the operation name.
now, in your project, you will send the variable (in this case id[uuid]) to the query handler and send this to your hasura server.
ask me if it is not clear for you.
A content-type "Product" having the following fields:
string title
int qty
string description
double price
Is there an API endpoint to retrieve the structure or schema of the "Product" content-type as opposed to getting the values?
For example: On endpoint localhost:1337/products, and response can be like:
[
{
field: "title",
type: "string",
other: "col-xs-12, col-5"
},
{
field: "qty",
type: "int"
},
{
field: "description",
type: "string"
},
{
field: "price",
type: "double"
}
]
where the structure of the schema or the table is sent instead of the actual values?
If not in Strapi CMS, is this possible on other headless CMS such as Hasura and Sanity?
You need to use Models, from the link:
Link is dead -> New link
Models are a representation of the database's structure. They are split into two separate files. A JavaScript file that contains the model options (e.g: lifecycle hooks), and a JSON file that represents the data structure stored in the database.
This is exactly what you are after.
The way I GET this info is by adding a custom endpoint - check my answers here for how to do this - https://stackoverflow.com/a/63283807/5064324 & https://stackoverflow.com/a/62634233/5064324.
For handlers you can do something like:
async getProductModel(ctx) {
return strapi.models['product'].allAttributes;
}
I needed the solution for all Content Types so I made a plugin with /modelStructure/* endpoints where you can supply the model name and then pass to a handler:
//more generic wrapper
async getModel(ctx) {
const { model } = ctx.params;
let data = strapi.models[model].allAttributes;
return data;
},
async getProductModel(ctx) {
ctx.params['model'] = "product"
return this.getModel(ctx)
},
//define all endpoints you need, like maybe a Page content type
async getPageModel(ctx) {
ctx.params['model'] = "page"
return this.getModel(ctx)
},
//finally I ended up writing a `allModels` handler
async getAllModels(ctx) {
Object.keys(strapi.models).forEach(key => {
//iterate through all models
//possibly filter some models
//iterate through all fields
Object.keys(strapi.models[key].allAttributes).forEach(fieldKey => {
//build the response - iterate through models and all their fields
}
}
//return your desired custom response
}
Comments & questions welcome
This answer pointed me in the right direction, but strapi.models was undefined for me on strapi 4.4.3.
What worked for me was a controller like so:
async getFields(ctx) {
const model = strapi.db.config.models.find( model => model.collectionName === 'clients' );
return model.attributes;
},
Where clients is replaced by the plural name of your content-type.
I know that servers communicate by POSTing to an inbox and outbox. But what's the URL for the inbox and outbox?
How to get the inbox or outbox URL
The URL is whatever the implementing server says it is. So it's different for each ActivityPub server.
The inbox and outbox URL for an actor is defined in the JSON-LD document for an actor:
{
"#context": ["https://www.w3.org/ns/activitystreams",
{"#language": "ja"}],
"type": "Person",
"id": "https://kenzoishii.example.com/",
// Right here!
"inbox": "https://kenzoishii.example.com/inbox.json",
"outbox": "https://kenzoishii.example.com/feed.json",
...
}
This also means that the inbox and outbox can be actor-specific, not just server specific.
How to get the actor JSON
Some ActivityPub sites like Mastodon make use of Webfinger to standardize a URL that can be used to get an actor's JSON-LD doc:
/.well-known/webfinger?resource=acct:foo#example.org
In this case, if you wanted to know the inbox for flaque#mastodon.social, you would first query the webfinger:
GET https://mastodon.technology/.well-known/webfinger?resource=acct:flaque#mastodon.technology
That would give you a JSON object like this:
{
subject: "acct:Flaque#mastodon.technology",
links: [
{
rel: "self",
type: "application/activity+json",
href: "https://mastodon.technology/users/Flaque"
}
]
}
With that href: https://mastodon.technology/users/Flaque, you can get the JSON representation with:
https://mastodon.technology/users/Flaque.json
(Note the .json!)
That would then give you a full actor object, which would include the inbox and outbox:
{
"inbox": "https://mastodon.technology/users/Flaque/inbox",
"outbox": "https://mastodon.technology/users/Flaque/outbox",
...
}
I'm developping a Loopback application extending base User model to UserCode model where each user is identified by an email plus a code fields.
So that a user can register with the same email twice but with different code.
I've seen that in node_modules/loopback/common/models/user.js at line 691 there is:
UserModel.validatesUniquenessOf('email', {message: 'Email already exists'});
I want to delete this restriction/validation but without change loopback code, of course.
How can I do it?
Maybe in the boot script I can loop through all validation and delete this one?
Figured it out
In this case you need to remove the default validations set by the User model
common/models/userCode.js
module.exports = function(UserCode){
//Add this line and it will start receiving multiple email.
delete UserCode.validations.email;
}
Also you can play with the required:true|false property to make any default defined property required or not.
common/models/userCode.json
{
"name": "UserCode",
"base": "User",
"idInjection": true,
"properties": {
"password": {
"type": "string",
"required": true
},
....
....
}
The following code the accepted answer will remove ALL the email validations:
module.exports = function(UserCode){
//Add this line and it will start receiving multiple email.
delete UserCode.validations.email;
}
Instead be selective and do something like this:
module.exports = function(UserCode){
// remove ONLY email uniqueness validation
UserCode.validations.email = UserCode.validations.email.reduce((all, one) => {
if (one.validation !== 'uniqueness') {
all.push(one);
}
return all;
}, []);
}
I'm trying to use the internationalization feature of sails based on i18n.
In my controller it works well. However, I would like to setup this in my model definition.
Please see the code below:
module.exports = {
attributes: {
name:{
type:'string',
required:true,
displayName: sails.__("test")
},
....
Unfortunately it does not work. I have the error below:
displayName: sails.__("test")
^
TypeError: Object [a Sails app] has no method '__'
Would you have an idea?
Any help will be very much appreciated.
Thanks,
displayName: sails.__("test")
You are trying to invoke the internationalization function statically; that is, you're seeing the error because you're running that function the moment your .js file is require()d by node.js, and before sails has finished loading.
There are two ways you can go about solving this problem.
1. Translate the value on each query
If you'd like to store the original value of displayName, and instead internationalize it each time you query for the model, you can override toJSON().
Instead of writing custom code for every controller action that uses a particular model (including the "out of the box" blueprints), you can manipulate outgoing records by simply overriding the default toJSON function in your model.
For example:
attributes: {
name:{
type:'string',
required:true,
},
getDisplayName: function () {
return sails.__(this.name);
},
toJSON: function () {
var obj = this.toObject();
obj.displayName = sails.__(this.name);
return obj;
},
...
}
2. Translate the value before create
You can use the Waterline Lifecycle Callbacks to translate the value to a particular language before the model is saved to the databas
Sails exposes a handful of lifecycle callbacks on models that are called automatically before or after certain actions. For example, we sometimes use lifecycle callbacks for automatically encrypting a password before creating or updating an Account model.
attributes: {
name:{
type:'string',
required:true,
},
displayName: {
type: 'string'
},
...
},
beforeCreate: function (model, next) {
model.displayName = sails.__(model.name);
next();
}
This internationalized the value of displayName will now be set on your model before it is inserted into the database.
Let me know how this works out for you.
Your solution is interesting. However, my wish would be to have a display name for each properties.
module.exports = {
attributes: {
name:{
type:'string',
required:true,
displayName: "Your great name"
},
adress:{
type:'string',
required:true,
displayName: "Where do you live?"
},
....
So is there a simple or clean solution to apply sails.__( foreach properties display name of the attribute?
Thanks,