I'm creating a chat app where data are stored like the image given below.
I want to add a validation rule for "Text" to be less than 300 characters long. I used the validation rule:
"TextChannels":{
"Anime_Weebs":{
".validate": "newData.child('Text').val().length < 200"
},
But this rule is not working. Instead any type of text is getting rejected.
This is my database:
And this is my Flutter AddMessage code:
_addMessage() {
final user = FirebaseAuth.instance.currentUser;
final time = DateTime.now().toUtc().millisecondsSinceEpoch;
_dbRef
.ref('TextChannels/${channelName}/Messages/')
.child('${time.toString()}${user!.uid}')
.set({
'Author': user.displayName,
'Author_ID': user.uid,
'Author_Username': username,
'Time': time,
'Text': _message,
'Delivered': false,
'Day': days
});
}
This rule that you have:
"TextChannels":{
"Anime_Weebs":{
".validate": "newData.child('Text').val().length < 200"
},
This rule says that your database has a path TextChannels/Anime_Weebs that has a child node Text whose length you want to restrict. But if we look at your database, there is no path TextChannels/Anime_Weebs/Text - and since a non-existing value is definitely not longer than 200 characters, the rule doesn't block any write operation.
If you want to apply a rule to all child nodes under a path, you need to use a wildcard variable to capture the unknown part of the path:
"TextChannels":{
"Anime_Weebs":{
"Messages": {
"$messageId": {
".validate": "newData.child('Text').val().length < 200"
}
}
},
So here the Messages and $messageId rules are new compared to your rules, and the latter indicates the the .validate rule should be applied to each child node of TextChannels/Anime_Weebs/Messages.
Related
Currently I'm writing an example script to get the hang of the suiteScript environment and get a better idea of how it all works. Right now I've written a script that looks like it should create and save a new customer record upon deployment. I dont run into any errors loading the script into NetSuite and I put the code through a debugger and the syntax seems right.
'''
/**
* #NApiVersion 2.x
* #NScriptType ClientScript
*/
define(["N/record"], function (r) {
function creatCustomer() {
var customer = record.create({
type: record.Type.CUSTOMER,
isDynamic: false,
defaultValues: null
});
customer.setValue(
{fieldId: "firstname", value: "James", ignoreFieldChange: false},
{fieldId: "lastname", value: "Halpert"},
{fieldId: "subsidiary",value: "Training 7"},
{fieldId: "email", value: "JHalpert#DM.com"},
{fieldId: "company Name",value: "Dunder Mifflin Test"}
);
customer.save({
enableSourcing: true,
ignoreMandatoryFields: false
});
}
return {
saveRecord: creatCustomer
};
});
'''
I think the problem might be deployment, but I don't know exactly what it could be. The script gets deployed but I can't find the customer record anywhere.
You should use the Netsuite help to read about script types and script type entry points. In particular client scripts are not generally used the way you have set that up.
This question has almost nothing in it regarding script deployment issues. (I'd expect to see at least one screen shot of the deployment screen)
For what you have written each time one of the deployment target records is saved a new customer record save will be attempted.
However the script you posted will error because:
the subsidiary value should be the internalid of the subsidiary - not the subsidiary name.
you are declaring 'N/record' as function(r) but then using it like record.create
record.setValue doesn't take a list of fieldId/value pairs
It may error if the user submitting the record doesn't have permissions to create customers.
It will likely error the second time it runs because a non-unique user name is being given. (Though this depends on how your account is configured)
One way to 'play' with scripts is to open a console window on any record in edit mode (and for some other screens) and you can run individual scripts like:
require(["N/record"], function (record) {
var customer = record.create({
type: record.Type.CUSTOMER,
isDynamic: false,
defaultValues: null
});
customer.setValue({fieldId: "firstname", value: "James"}); //, ignoreFieldChange: false}, <- this is for dynamic mode and client scripts
customer.setValue({fieldId: "lastname", value: "Halpert"});
customer.setValue({fieldId: "subsidiary",value: "Training 7"});
customer.setValue({fieldId: "email", value: "JHalpert#DM.com"});
customer.setValue({fieldId: "company Name",value: "Dunder Mifflin Test"});
var custId = customer.save({
enableSourcing: true,
ignoreMandatoryFields: false
});
console.log('created customer ', custId);
});
Consider the following rule:
match /triplogs/{anyDocument} {
allow create: if request.auth != null;
allow read, update, delete: if request.auth.uid == resource.data.userId;
}
If I hit that with my logged in user, I get the standard:
Error: FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
A few points to know:
I am indeed logged in, I can output the UID of the current user and it is correct.
I can hit this same route via the mobile version and it works.
If I remove the resource.data.userId check, and just check to see if the user is authenticated (even hard code a uid), it works (so the rule seems to get checked against appropriately)
The simulator says "resources" is null. I don't understand how the resources object could be null, maybe it's something with the simulator?
Any help would be appreciated, I've troubleshot and googled around for the last few hours with no success.
Query in question:
// ... My firebase wrapper:
import { firebaseApp } from '../utils/firebase';
// ...
const collectionName = 'triplogs';
export const fetchTrips = async (filters?: Filter[]) => {
let query;
const ref = db?.collection(collectionName);
const trips: TripLog[] = [];
if (filters) {
filters.forEach((filter) => {
query = ref?.where(filter.field, filter.operator, filter.value);
});
}
try {
const obj = query || ref;
console.log(firebaseApp()?.auth().currentUser?.uid); // <-- this is populated with my logged in UID, FWIW
const docs = await obj?.get();
docs?.forEach((doc) => {
const data = doc.data() as TripLog;
trips.push({
...data,
id: doc.id
});
});
return trips;
} catch (err) {
throw new Error(err);
}
};
Here's the info when I try to run a sample request in the simulator, against those auth rules:
{
"path": "/databases/%28default%29/documents/triplogs/%7BanyDocument%7D"
"method": "get"
"auth": {
"uid": "5ompaySrXQcL9veWr3QlSujwlDS2"
"token": {
"sub": "5ompaySrXQcL9veWr3QlSujwlDS2"
"aud": "....omitted"
"firebase": {
"sign_in_provider": "google.com"
}
"email": "...omitted..."
"email_verified": true
"phone_number": ""
"name": "...."
}
}
"time": "2020-10-30T16:48:39.601Z"
}
The error in the sim is:
Error: simulator.rules line [32], column [58]. Null value error.
^^ Which is the resource object
If I change the auth rules to be the following, then I will get back data
allow read, update, delete: if request.auth.uid != null;
Data like:
[{
"createdAt": 1597527979495,
"name": "Foo Bar Trip",
"date": 1597527979495,
"userId": "5ompaySrXQcL9veWr3QlSujwlDS2",
"id": "FnH2E9WfDkpRHPLXxlDy"
}]
But as soon as I check against a field, the "resources" object is null
allow read, update, delete: if request.auth.uid == resource.data.userId;
I'll note you seem to be querying a collection, but your rules seem written for a document... as stated here , rules are not filters - they do not separate out individual documents in a collection. Are you doing that in your filter[array]?
Don't know what else might be going on, but your processing of the filter Array (I do a similar thing) won't work - you are REPLACING the query on each loop, not extending it. You might try
query = ref;
if (filters) {
filters.forEach((filter) => {
query = query?.where(filter.field, filter.operator, filter.value);
});
}
to extend the query.
Also, unless your wrapper does more than it shows, your line:
const docs = await obj?.get();
"gets" the QuerySnapshot, not the array of docs. You'd have to do
const docs = await obj?.get().docs;
to get the array of actual docs.
Finally, we have no idea (from your above snippets) whether the userID field is even present in your Firestore documents
Solution
I figured it out. I incorrectly assumed the query I was making was auto-filtered by the auth rule (in this case request.auth.id === userId) ... but the auth middleware to firestore doesn't do that. Makes sense. So I just added my filter ... something like this:
fetchTrips([{
field: 'userId',
operator: '==',
value: user.uid,
}]);
This works and returns the correct results. Trying to change the userID correctly throws the auth error too. So that was it.
I am able to retrieve records for a particular Incident ID using Invoke-RestMethod. However, while retrieving the data, values like Resolved To, Updated By, etc. get populated by a sysid.
Resolved By comes in this format:
https<!>://devinstance.servicenow.com/api/sysid, value= sysid
I would like to view the username instead of the sysid.
The 'User ID' (user_name) isn't on the Incident, it's on the sys_user table, so you'll have to dot-walk to it.
If you're using the table API, you'll need to specify a dot-walked field to return, using the sysparm_fields query parameter.
This is no problem, just specify your endpoint like this:
$uri = "https://YOUR_INSTANCE.service-now.com/api/now/table/incident?sysparm_query=number%3DINC0000001&sysparm_fields=resolved_by.user_name"
I've specified a query for a specific incident number is requested, but you can replace that with whatever your query is.The important part is sysparm_fields=resolved_by.user_name. You'll want to specify any other fields you need here, as well.
The JSON I get as a result of running this API call, is the following:
{
"result": [
{
"resolved_by.user_name": "admin"
}
]
}
Note the element name: "resolved_by.user_name".
Another option for doing this, would be to tell the API to return both display, and actual values by specifying the sysparm_display_value parameter and setting it to all to return both sys_id and display value, or just true to return only display values.
Your URI would then look like this:
https://dev12567.service-now.com/api/now/table/incident?sysparm_query=resolved_byISNOTEMPTY%5Enumber%3DINC0000001&sysparm_display_value=all
And your JSON would contain the following:
"number": {
"display_value": "INC0000001",
"value": "INC0000001"
},
"resolved_by": {
"display_value": "System Administrator",
"link": "https://YOUR_INSTANCE.service-now.com/api/now/table/sys_user/6816f79cc0a8016401c5a33be04be441",
"value": "6816f79cc0a8016401c5a33be04be441"
},
"sys_updated_by": {
"display_value": "admin",
"value": "admin"
},
This would be accessed by:
answer.result[n].resolved_by.display_value
Greeting everyone, I have a datatable in my html page that I populated using REST API. I can create new row and also update or delete by selecting a row and clicking the edit or delete button.
But currently I am unable to delete update or delete multiple row at once due to url error,
e.g : PUT http://127.0.0.1:8000/dashboard/content_detail/5,7,9/ 404 (Not Found)
how can I split this this into several separate url with respective id when I update or delete.
e.g :
/dashboard/content_detail/5
/dashboard/content_detail/7
/dashboard/content_detail/9
Below is my code, any help is much appreciated thank you.
idSrc: 'id',
ajax: {
create: {
type: 'POST',
url: content_path,
data: function (content_data) {
var create_data = {};
$.each(content_data.data, function (id, value) {
create_data['name'] = value['name'];
create_data['description'] = value['description'];
create_data['category'] = value['category'];
});
return create_data;
},
success: function () {
content_table.api().ajax.reload();
}
},
edit: {
type: 'PUT',
url: '/dashboard/content_detail/_id_/',
data: function (content_data) {
var updated_data = {};
$.each(content_data.data, function (id, value) {
updated_data['description'] = value['description'];
updated_data['category'] = value['category'];
updated_data['name'] = value['name'];
});
return updated_data;
},
success: function () {
content_table.api().ajax.reload();
}
},
remove: {
type: 'DELETE',
url: '/dashboard/content_detail/_id_/',
data: function (content_data) {
var deleted_data = {};
$.each(content_data.data, function (id, value) {
deleted_data['id'] = id;
});
return deleted_data;
},
success: function () {
content_table.api().ajax.reload();
}
}
},
If you're going to allow the update of a large number of items at once, then PATCH might be your friend:
Looking at the RFC 6902 (which defines the Patch standard), from the client's perspective the API could be called like
PATCH /authors/{authorId}/book
[
{ "op": "replace", "path": "/dashboard/content_detail/5", "value": "test"},
{ "op": "remove", "path": "/dashboard/content_detail", "value": [ "7", "9" ]}
]
From a design perspective you don't want several ids in your url.
I would prefer single calls for each change, thinking in resources you only manipulate one at a time.
In case this is a perfomance issue, I recommend a special url marked with action or something simliar, to make clear this ist not REST.
In HTTP it is not required for information to only exist on a single resource. It is possible to have multiple resources that represent the same underlying data.
It's therefore not out of the question to create a resource that 'represents' a set of other resources that you wish to DELETE or PUT to.
I do agree that it might not be the most desirable. I think we tend to prefer having information only exist in a single part of tree, and I think we like to avoid situations where updating a resource effects a secondary resource's state. However, if you are looking for a strictly RESTful solution to solve this problem, I think it's the right way.
Therefore a url design such as:
/dashboard/content_detail/5,7,9/
Is not necessarily non-RESTful or goes against the HTTP protocol. The fact that you're getting a 404 on that URL currently has to do with your application framework, not the protocol (HTTP) or architecture (REST) of your API.
However, for cases such as these I feel I would personally be inclined to sometimes create a separate POST endpoint that, acting outside of REST like an RPC endpoint. Specifically for these types of batch requests.
I am currently building an API which uses the JSON patch specification to do partial updates to MongoDB using the Mongoose ORM.
I am using the node module mongoose-json-patch to apply patches to my documents like so:
var patchUpdate = function(req, res){
var patches = req.body;
var id = req.params.id;
User.findById(id, function(err, user){
if(err){ res.send(err);}
user.patch(patches, function(err){
if(err){ res.send(err);}
user.save(function(err){
if(err) {res.send(err);}
else {res.send("Update(s) successful" + user);}
});
});
});
};
My main issues occur when I am trying to remove or replace array elements with the JSON patch syntax:
var patches = [{"op":"replace", "path": "/interests/0", "value":"Working"}]
var user = {
name: "Chad",
interests: ["Walking", "Eating", "Driving"]
}
This should replace the first item in the array ("Walking") with the new value ("Working"), however I can't figure out how to validate what is actually being replaced. If another request removed /interests/0 prior to the patch being applied, "Eating" would be replaced by "Working" instead of "Walking", which would no longer exist in the array.
I would like to be sure that if the client thinks he is editing "Walking", then he will either successfully edit it, or at least get an error.
After running into the same issue like this myself i'll share my solution. The spec (described here) describes six operations, one of which is test. The source describes the test operation as
Tests that the specified value is set in the document. If the test fails, then the patch as a whole should not apply.
To ensure that you're changing the values that you're expecting you should validate the state of the data. You do this by preceeding your replace or remove operation with a test operation, where the value is equal to the expected data state. If the test fails, the following operations will not be executed.
With the test operation your patch data will look like this:
var patches = [
{"op":"test", "path": "/interests/0", "value": currentValue}, //where currentValue is the expected value
{"op":"replace", "path": "/interests/0", "value":"Working"}
]