How make permission for change attribute in Sails.js? - sails.js

For example: we have the model Profile. Each user profile has attribute alias_name that can be set only once (when you create), and can not be changed (when you update).
Of course I can override action .update() in the controller and remove the attribute from req.body. But then lost all the magic Blueprint API.
More I can create special policies that will remove the attribute from req.body. But not sure it's right.
Perhaps it should be made in the method .beforeUpdate() in the Profile model?
How better to do? Share your experiences?

You can use approach with custom validation rule for this attribute
// /api/models/profile.js
module.exports = {
attributes: {
alias_name: {
type: 'string',
nonEditable: true
}
},
// ...
types: {
nonEditable: function(prop) {
return prop === null;
}
}
}
Thus, this fields can be changed only once (in case you have not defaultsTo specified for alias_name).

Related

Shopware 6: sw-inherit-wrapper property currentValue does not update on select

I've a custom entity enterprise, which is assigned to product.extensions.myPlugin.enterpriseId.
Now i want to select the enterprise within Shopware Vue-Component sw-entity-single-select wrapped by sw-inherit-wrapper:
<sw-inherit-wrapper
v-model="product.extensions.myPlugin.enterpriseId"
:has-parent="false"
:inherited-value="null">
<template #content="{currentValue, updateCurrentValue, isInherited}">
<sw-entity-single-select
class="enterprise-name"
:key="isInherited"
:value="currentValue"
entity="enterprise"
:placeholder="$tc('inputEnterprise.placeholder')"
show-clearable-button
allow-entity-creation
:entity-creation-label="$tc('inputEnterprise.labelCreation')"
#change="updateCurrentValue"></sw-entity-single-select>
</template>
</sw-inherit-wrapper>
This template ist part of a modal.
On select a enterprise from given list, the <sw-entity-single-select__selection-text> does not update.
Step out step in into the template (close and reopen the parent modal), the enterprise is set and printed out on the <sw-entity-single-select__selection-text>.
So the entity extension was updated but not the property currentValue in <sw-entity-single-select__selection-text>
As example i follow the sw_product_basic_form_manufacturer_field
Question: Why property currentValue does not update in <sw-entity-single-select> on select from its list?
EDIT #1
The extension is set in overridden component sw-product-detail like this:
// sw-product-detail/index.js
...
Component.override('sw-product-detail', {
...
computed: {
myPluginRepository() {
return this.repositoryFactory.create('my_plugin');
},
}
watch: {
product() {
if(this.product.id) {
this.__setMyPluginExtension()
}
}
},
methods: {
...
__setMyPluginExtension() {
let extension = this.product.extensions.myPlugin ?? null;
if(null === extension) {
extension =
this.myPluginRepository.create(Shopware.Context.api);
extension.enterpriseId = null
}
this.product.extensions.myPlugin = extension;
},
}
}
Inside the overridden child component sw-product-detail-base the modal is loaded.
I think, the extension is known when the component is loaded and rendered. I've check it with a debug breakpoint inside the created() in the custom component, the extension is known like defined in parent component.
Even more, if i set extension.enterpriseId a real default value instead null then the enterprise is printed out in <sw-entity-single-select__selection-text>, what me indicate, the extension is known.
Btw, i used the change method, but i'm looking for my error...
Best guess without knowing more of the stack would be that this.product.extensions.myPlugin.enterpriseId isn't reactive. That would be the case if the property doesn't exists when the product is originally set and it is declared like this:
this.product.extensions.myPlugin = {
enterpriseId: 'asdf'
};
Any further changes to enterpriseId would then not be reactive and the view would subsequently not be updated when it changes.
To be safe you could add a #change listener to sw-inherit-wrapper and update the property reactively:
onEnterpriseIdChange(enterpriseId) {
if (!this.product.extensions.myPlugin) {
this.$set(this.product.extensions, 'myPlugin', {});
}
if (!this.product.extensions.myPlugin.enterpriseId) {
this.$set(this.product.extensions.myPlugin, 'enterpriseId', enterpriseId);
}
}
As addition of #dneustadt answers:
...
const extension = this.myPluginRepository.create(Shopware.Context.api);
extension.enterpriseId= null
this.$set(this.product.extensions, 'myPlugin', extension)
...
Use the repository create() method instead create a simple object because of required method entity.getEntityName() in changeset-generator.data.js

How to give a fixed Uid to my Action

Hy,
I'm trying to call my action with allways a fixed Uid (configured by TS) so I could put a plugin on my page to register for a specific Event. And don't have to go over a Event List click the Event click register.
I tried the following which did not work out:
public function newAction(
\XYZ\xyz\Domain\Model\Registration $newRegistration = NULL,
\XYZ\xyz\Domain\Model\Event $event = 'DD8B2164290B40DA240D843095A29904'
)
The next didn't one work either!
public function newAction(
\XYZ\xyz\Domain\Model\Registration $newRegistration = NULL,
\XYZ\xyz\Domain\Model\Event $event = Null
) {
$myinstance = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
'XYZ\\xyz\\Domain\\Model\\Event'
);
$event = $myinstance->findByUid('DD8B2164290B40DA240D843095A29904');
.......
}
So I was woundering is there a way to give my fixed Uid to the action?
In TYPO3 calling Extbase actions is done in the routing and dispatching components - to pass anything from the outside that is different from a numeric uid value a custom property TypeConverter would have to be implemented that transforms a particular string pattern into a value domain object of type Event.
However, there's a simpler approach by using configuration:
1) Provide configuration in TypoScript
Extbase uses a strong naming convention based on the extension name and optionally the plugin name. Thus, either tx_myextension or tx_myextension_someplugin can be used - latter is more specific for for according somePlugin. Besides that settings are automatically forwarded and provided in an Extbase controller context - accessible by $this->settings.
plugin.tx_xyz {
settings {
newActionEventIdentifier = DD8B2164290B40DA240D843095A29904
}
}
2) Retrieve data via repository
\XYZ\xyz\Domain\Repository\EventRepository
Use a dedicated EventRepository::findByIdentifier(string) method to retrieve the data. The property names are just assumptions since there are no explicit mentions how exactly the event data is persisted and whether it is persisted in a relational DBMS at all.
<?php
namespace XYZ\xyz\Domain\Repository;
class EventRepository
{
public function findByIdentifier($identifier)
{
$query = $this->createQuery();
$query->matching(
$query->equals('event_id', $identifier)
);
return $query->execute();
}
}
3) Putting all together in the according controller
The $event property was removed from the action since that entity is pre-defined and cannot be submitted from the outside (and to support the string to Event entity transformation a custom TypeConverter would be required as mentioned earlier).
public function newAction(
\XYZ\xyz\Domain\Model\Registration $newRegistration = null
) {
$event = $this->eventRepository->findByIdentifier(
$this->settings['newActionEventIdentifier']
);
if ($event === null) {
throw new \RuntimeException('No event found', 1522070079);
}
// the regular controller tasks
$this->view->assign(...);
}

Grails rest-api app to handle multiple params

Using Grails 3.1.3, I created a rest-api so that I am able to capture GET requests that not only query for one parameter, but multiple if needed. I don't know how to code this correctly inside the UrlMappings file. Here are the details.
Domain class:
class ProdDetail {
Integer pid
String title
String category
Integer year
}
And some of these inside the BootStrap:
new ProdDetail(pid:'101', title:'No Highway', author:'Nevil Shute', category:'fiction', year:1948).save(failOnError:true)
new ProdDetail(pid:'214', title:'In the Country of Men', author:'Hisham Matar', category:'misery', year:2007).save(failOnError:true)
Controller:
protected List<ProdDetail> listAllResources(Map params) {
println params
try {
ProdDetail.where {
if (params.category && params.maxYear) {
category == params.category && year <= params.int('maxYear')
} else if (params.category) {
category == params.category
} else if (params.maxYear) {
year <= params.int('maxYear')
} else {
pid > 0
}
}.list()
} catch (Exception e) {
[]
}
}
UrlMappings:
static mappings = {
"/prodDetails"(resources:'prodDetail')
"/prodDetails/category/$category?"(controller:'prodDetail', action:'index')
"/prodDetails/yearUnder/$maxYear?"(controller:'prodDetail', action:'index')
// the line below is not right I think, what's the correct format?
"/prodDetails/combo/$category?&$maxYear?"(controller:'prodDetail', action:'index')
}
Now, where as these two curls would work:
curl localhost:8080/prodDetails/category/misery
curl localhost:8080/prodDetails/yearUnder/2007
This one fails to go into the desired clause in the controller to detect both params:
curl localhost:8080/prodDetails/combo/?category=misery&maxYear=2007
It just detects 'category' but not the 'maxYear' which it considers as 'null'.
How can I cater for such a curl please?
It kind of depends on what you want your URLs to look like, but assuming you want your requests to look like this:
http://localhost:8080/prodDetails/combo/misery?maxYear=2007&title=common
The UrlMappings should look like
static mappings = {
"/prodDetails/combo/$category"(controller:'prodDetail', action:'index')
}
Then the params object in the controller should have both whatever's in the place of $category, in this example misery, and the other parameters after the ? as well.
If you want the parameters to be in the path you can do this:
static mappings = {
"/prodDetails/combo/$category/$title/$maxYear"(controller:'prodDetail', action:'index')
}
And the request would then be:
http://localhost:8080/prodDetails/combo/misery/common/2007
One other option would be to use a command object. So if you had:
static mappings = {
"/prodDetails/combosearch"(controller:'prodDetail', action:'comboSearch')
}
And then created an object beside the controller called ComboSearchCommand.groovy that looked like:
import grails.validation.Validateable
class ComboSearchCommand implements Validetable {
String category
String title
int maxYear
static constraints = {
category blank: false, nullable: true
title blank: false, nullable: true
maxYear blank: false, nullable: true
}
}
(Which you can do validation on just like a domain object)
And then in your controller you have the method take the command object instead of params
protected List<ProdDetail> comboSearch(ComboSearchCommand command) {
println command.category
}
Then your URL would be
http://localhost:8080/prodDetails/combosearch?category=misery&maxYear=2007&title=common
And the parameters will bind to the command object.
I've used that quite a bit, you can share validations or have your command object inherit validations from domain objects, lots of flexibility.
https://grails.github.io/grails-doc/latest/guide/single.html#commandObjects
You don't need to specify the parameters in UrlMappings if those params are not part of the URL:
No need of this:
"/prodDetails/combo/$category&?$maxYear?"(controller:'prodDetail', action:'index')
Yes you need this to match the URL to a controller/action (but remove the ?)
"/prodDetails/yearUnder/$maxYear?"(controller:'prodDetail', action:'index')
Also, you don't need Map params in listAllResources(Map params)
"params" is an injected property of controllers, the println params will work OK with: listAllResources()
What I would do is to define:
listAllResources(String category, int maxYear, ...) where ... are all the params that action can receive, most would be optional, so you will receive a null value if not included in your request.
Remember: UrlMappings are to map URLs to controller/actions, and you have the same controller/action, so I would remove all the mappings and process the optional parameters in the action just checking which are null or not.
Edit (considering comments)
Q: the method is not overloaded to handle params like that
A: methods are dynamic, this is Grails / Groovy, not Java. It will call the action method even if all the params are null. I would recommend you to go through the Grails controller documentation in detail.
Q: found that the listAllResources method was never called
A: remove the protected keyword from the action, only subclasses would be able to invoke that method. Also, you can add an UrlMapping to avoid users to invoke that URL (match the URL and return 404 Not Available or something like that)
Q: I want to handle a GET request like this localhost:8080/prodDetails/combo?category=misery&year=2016&title=commonTitle, how exactly should the i) entry in UrlMappings, and ii) the listAllResources method look like?
A:
static mappings = {
// if "compo" comes in the action portion, map that to the listAllResources method
// as I said, if all parameters comes in the query string, no further actions are needed, if you need parameters to be part of the URL path, then you need to play with the $xxxx in the URL
"/prodDetails/combo"(controller:'prodDetail', action:'listAllResources')
}
def listAllResources()
{
println params
// logic here
render "OK"
}
Check:
https://grails.github.io/grails-doc/latest/ref/Controllers/params.html
https://grails.github.io/grails-doc/latest/ref/Controllers/render.html
How does grails pass arguments to controller methods?

Sequelize Hooks - does the funcs expose the Models as in the associations?

When creating a hook I want to know if the models variable is exposed in the function like in the assosiations:
...
classMethods: {
associate: function ( models ) {
users.belongsTo( models.roles, { foreignKey: 'role' } );
}
}
...
What I'd like to do is to update the a value of another model, first I have to update the current amount of product adding the amount purchased (any tips?), so, I need the models var.
hooks : {
afterCreate: function ( inventory, options ) {
//What does the `options` value has?
}
}
One workaround suggested was to simply require the model...
Also I would like to know if afterCreate would work for bulkCreate? Or does it necessary has to be afterBulkCreate?
afterCreate is triggered after each creation. afterBulkCreate is triggered after the whole set has been created. So if you are creating 10 items, afterCreate would be called 10 times, after each item was created. But afterBulkCreate would only be called once.
This doesn't seem to be documented throughly enough but I'd suggest just creating a hook and then doing console.log(arguments) inside the hook to see which arguments are beeing passed in. But I'd suggest to just require the model like you said.
The models variable is not exposed in the function. The solution I found when I need to update the value of another model is the following:
Define a function to get the hooks that receives the models:
var getHooks = function(models) {
return {
afterCreate: function(item, options) {
// Can access the models:
models.roles.create({...});
},
beforeCreate: function(item, options) {
// Code here
}
};
};
And set the hooks functions in the associate method:
classMethods: {
associate: function ( models ) {
users.belongsTo( models.roles, { foreignKey: 'role' } );
var hooks = getHooks(models);
for(var name in hooks) {
users.addHook(name, hooks[name]);
}
}
}

Yii2: how to use custom validation function for activeform?

In my form's model, I have a custom validation function for a field defined in this way
class SignupForm extends Model
{
public function rules()
{
return [
['birth_date', 'checkDateFormat'],
// other rules
];
}
public function checkDateFormat($attribute, $params)
{
// no real check at the moment to be sure that the error is triggered
$this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
}
}
The error message doesn't appear under the field in the form view when I push the submit button, while other rules like the required email and password appear.
I'm working on the Signup native form, so to be sure that it is not a filed problem, I've set the rule
['username', 'checkDateFormat']
and removed all the other rules related to the username field, but the message doesn't appear either for it.
I've tried passing nothing as parameters to checkDateFormat, I've tried to explicitly pass the field's name to addError()
$this->addError('username', '....');
but nothing appears.
Which is the correct way to set a custom validation function?
Did you read documentation?
According to the above validation steps, an attribute will be
validated if and only if it is an active attribute declared in
scenarios() and is associated with one or multiple active rules
declared in rules().
So your code should looks like:
class SignupForm extends Model
{
public function rules()
{
return [
['birth_date', 'checkDateFormat'],
// other rules
];
}
public function scenarios()
{
$scenarios = [
'some_scenario' => ['birth_date'],
];
return array_merge(parent::scenarios(), $scenarios);
}
public function checkDateFormat($attribute, $params)
{
// no real check at the moment to be sure that the error is triggered
$this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
}
}
And in controller set scenario, example:
$signupForm = new SignupForm(['scenario' => 'some_scenario']);
Try forcing the validation on empty field
['birth_date', 'checkDateFormat', 'skipOnEmpty' => false, 'skipOnError' => false],
Also, make sure you don't assign id to your birth_date field in your view.
If you do have id for your birth_date, you need to specify the selectors
<?= $form->field($model, 'birth_date', ['selectors' => ['input' => '#myBirthDate']])->textInput(['id' => 'myBirthDate']) ?>
To make custom validations in yii 2 , you can write custom function in model and assign that function in rule.
for eg. I have to apply password criteria in password field then I will write like this in model.
public function rules()
{
return [
['new_password','passwordCriteria'],
];
}
public function passwordCriteria()
{
if(!empty($this->new_password)){
if(strlen($this->new_password)<8){
$this->addError('new_password','Password must contains eight letters one digit and one character.');
}
else{
if(!preg_match('/[0-9]/',$this->new_password)){
$this->addError('new_password','Password must contain one digit.');
}
if(!preg_match('/[a-zA-Z]/', $this->new_password)){
$this->addError('new_password','Password must contain one character.');
}
}
}
}
You need to trigger $model->validate() somewhere if you are extending from class Model.
I stumbled on this when using the CRUD generator. The generated actionCreate() function doesn't include a model validation call so custom validators never get called. Also, the _form doesn't include and error summary.
So add the error summary to the _form.
<?= $form->errorSummary($model); ?>
...and add the validation call - $model->validate() - to the controller action
public function actionCreate()
{
$model = new YourModel();
if ($model->load(Yii::$app->request->post()) && $model->validate()) {...
Although it's an old post i thought I should answer.
You should create a Custom Validator Class and to create a validator that supports client-side validation, you should implement the yii\validators\Validator::clientValidateAttribute() method which returns a piece of JavaScript code that performs the validation on the client-side. Within the JavaScript code.
You may use the following predefined variables:
attribute: the name of the attribute being validated.
value: the value being validated.
messages: an array used to hold the validation error messages for
the attribute.
deferred: an array which deferred objects can be pushed into
(explained in the next subsection).
SO that means you can use messages array to push your messages to the client end on runtime within the javascript code block in this method.
I will create a class that includes dummy checks that could be replaced the way you want them to. and change the namespace according to your yii2 advanced or basic.
Custom Client-side Validator
namespace common\components;
use yii\validators\Validator;
class DateFormatValidator extends Validator{
public function init() {
parent::init ();
$this->message = 'You entered an invalid date format.';
}
public function validateAttribute( $model , $attribute ) {
if ( /*SOME CONDITION TO CHECK*/) {
$model->addError ( $attribute , $this->message );
}
}
public function clientValidateAttribute( $model , $attribute , $view ) {
$message = json_encode ( $this->message , JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
return <<<JS
if ($("#DATE-1").val()=="" || $("#DATE-2").val() =="") {
messages.push($message);
}
JS;
}
}
and then inside your model SigupForm add the rule
['birth_date', 'common\components\DateFormatValidator'],
Deferred Validation
You can even add ajax calls inside the clientValidateAttribute function and on the base of the result of that ajax call you can push message to the client end but you can use the deferred object provided by yii that is an array of Deferred objects and you push your calls inside that array or explicitly create the Deferred Object and call its resolve() method.
Default Yii's deferred Object
public function clientValidateAttribute($model, $attribute, $view)
{
return <<<JS
deferred.push($.get("/check", {value: value}).done(function(data) {
if ('' !== data) {
messages.push(data);
}
}));
JS;
}
More about Deferred Validation
You need to render the model from controller. Without initializing the model in view. And in the controller you need to call the validate function
Are you sure the first parameter of addError shouldn't be like this
$this->addError(**'attribute'**, Yii::t('user', 'You entered an invalid date format.'));
I had common problem.
In your validation function:
public function checkDateFormat($attribute, $params)
{
// no real check at the moment to be sure that the error is triggered
$this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
}
$params doesn`t get any value at all. It actually always equals to Null. You have to check for your attribute value in function:
public function checkDateFormat($attribute, $params)
{
if($this->birth_date == False)
{
$this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
}
}
that`s how it worked for me.
If you don't use scenarios for your model, you must mark your atribute as 'safe':
['birth_date','safe'],
['birth_date', 'checkDateFormat'],
And, on the other hand, you can use this for date validation:
['birth_date','safe'],
[['birth_date'],'date', 'format'=>'php:Y-m-d'],
You can change format as you want.
**We should set attributes to the function to work with input value **
public function rules()
{
return [
['social_id','passwordCriteria'],
];
}
public function passwordCriteria($attribute, $params)
{
if(!empty($this->$attribute)){
$input_value = $this->$attribute;
//all good
}else{
//Error empty value
$this->addError('social_id','Error - value is empty');
}
}
Are you by any chance using client side validation? If you do then you have to write a javascript function that would validate the input. You can see how they do it here:
http://www.yiiframework.com/doc-2.0/guide-input-validation.html#conditional-validation
Another solution would be to disable client validation, use ajax validation, that should bring back the error too.
Also make sure that you have not overwritten the template of the input, meaning make sure you still have the {error} in there if you did overwrite it.
Your syntax on rules should be something like this man,
[['birth_date'], 'checkDateFormat']
not this
['birth_date', 'checkDateFormat']
So in your case, it should look like below
...
class SignupForm extends Model
{
public function rules()
{
// Notice the different with your previous code here
return [
[['birth_date'], 'checkDateFormat'],
// other rules
];
}
public function checkDateFormat($attribute, $params)
{
// no real check at the moment to be sure that the error is triggered
$this->addError($attribute, Yii::t('user', 'You entered an invalid date format.'));
}
}