Referencing a property on a model with node.js in Loopback - javascript

Trying to very reference a property in with loopback and considering how much time I have tried to do this, I am clearly missing some fundamental concept.
Very simply, I have a Question model which has a points as a integer property and what I would like to do is simply print out the points property to the console.
module.exports = function(Question) {
Question.observe('after save', function(ctx, next) {
console.log(Question.prototype.points)
next();
});
};
When I do the above, it prints out undefined
Considering that this is such a simple operation to do, what am I missing?
json file:
{
"name": "Question",
"plural": "Questions",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"text": {
"type": "string",
"required": true
},
"points": {
"type": "number",
"required": true
}
},
}

You are almost there. Use context object that you get on after save.
module.exports = function(Question) {
Question.observe('after save', function(ctx, next) {
console.log(ctx.instance.points);
next();
});
};
Operation hooks documentation.

Related

How do you make json $ref to local files?

I am using the AJV package in my node.js project.
I am trying to validate some data against a couple of schema files. Both of these schema files are in the same directory:
/dir
|
parent_schema.json
|
sub_schema.json
/data
|
data.json
I am trying to get a super simple example of the $ref property working but I am having trouble. parent_schema.json looks like:
{
"properties": {
"foo": { "type": "string" },
"bar": { "$ref": "sub_schema.json" }
}
}
And sub_schema.json looks like:
{
"properties": {
"sub1": { "type": "string" },
}
}
And I am trying to validate my data.json which for the sake of completeness looks like:
{
"foo": "whatever",
"bar": {
"sub1": "sometext"
}
}
The issue I'm having is with my $ref path. I am getting this error from AJV:
MissingRefError {
message: "can't resolve reference subschema1.json from id #"
missingRef: "subschema1.json"
missingSchema: "subschema1.json"
}
Anyone see what's wrong with my path? I know you are also supposed to use the # to select what specific property you want matched against, but I want the ENTIRE schema to be used.
It's a common misconception that $ref somehow "loads" a file.
See what ajv.js.org says:
$ref is resolved as the uri-reference using schema $id as the base URI (see the example).
And:
You don’t have to host your schema files at the URIs that you use as schema $id. These URIs are only used to identify the schemas, and according to JSON Schema specification validators should not expect to be able to download the schemas from these URIs.
Ajv won't try loading this schema from stack://over.flow/string for example:
{
"$id": "stack://over.flow/string",
"type": "string"
}
If you want to reference that schema in another schema, they both need to have the same base URI stack://over.flow/ e.g.,
{
"$id": "stack://over.flow/object",
"type": "object",
"properties": {
"a": { "$ref": "string#" }
}
}
Here { "$ref": "string#" } says "import the schema at stack://over.flow/string" so you end up with:
{
"$id": "stack://over.flow/object",
"type": "object",
"properties": {
"a": {
"$id": "stack://over.flow/string",
"type": "string"
}
}
}
This allows you to combine small schemas:
const ajv = new Ajv;
ajv.addSchema({
"$id": "stack://over.flow/string",
"type": "string"
});
ajv.addSchema({
"$id": "stack://over.flow/number",
"type": "number"
});
const is_string = ajv.getSchema("stack://over.flow/string");
const is_number = ajv.getSchema("stack://over.flow/number");
console.log(is_string('aaa'), is_string(42));
console.log(is_number('aaa'), is_number(42));
const is_ab = ajv.compile({
"$id": "stack://over.flow/object",
"type": "object",
"properties": {
"a": { "$ref": "string#" },
"b": { "$ref": "number#" }
}
});
console.log(is_ab({a: "aaa", b: 42}));
console.log(is_ab({a: 42, b: "aaa"}));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ajv/6.12.2/ajv.min.js"></script>
(Please note that in your example both schemas are incorrect. You're missing {"type": "object"} in both.)
To answer your question:
const ajv = new Ajv;
ajv.addSchema({
"$id": "stack://over.flow/parent.schema",
"type": "object",
"properties": {
"foo": { "type": "string" },
"bar": { "$ref": "child.schema#" }
}
});
ajv.addSchema({
"$id": "stack://over.flow/child.schema",
"type": "object",
"properties": {
"sub1": { "type": "string" },
}
});
const is_parent = ajv.getSchema("stack://over.flow/parent.schema");
const is_child = ajv.getSchema("stack://over.flow/child.schema");
console.log(is_parent({
"foo": "whatever",
"bar": {
"sub1": "sometext"
}
}));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ajv/6.12.2/ajv.min.js"></script>

Trying to access dot notation variable with a string

I'm working on creating a web form that can dynamically read a swagger endpoint to create form fields. Specifically right now I am trying to read the schemas from the component section defined by openAPI 3.
Example json:
{
"openapi": "3.0.1",
"info": {
.......
},
"paths": {
........
},
"components": {
"schemas": {
"FakeAppConfiguration": {
"type": "object",
"properties": {
"setting1": {
"type": "string",
"nullable": true
},
"setting2": {
"type": "string",
"nullable": true
}
},
"additionalProperties": false
},
"OtherFakeAppConfiguration": {
........
},
"ThirdFakeAppConfiguration": {
........
}
}
}
}
}
Using this snippet of json as an example, I can easily get the names of the schemas that are defined by using (json has already been loaded into data using fetch)
for (let schema in data.components.schemas)
{
//this will print out FakeAppConfiguration, OtherFakeAppConfiguration, ThirdFakeAppConfiguration
console.log(schema);
}
My problem now comes in trying to access each of these schema trees without calling them directly. I could easily do data.components.schemas.FakeAppConfiguration, but that would defeat the purpose of making this dynamic. I've been trying to somehow use the strings obtained in the above loop to access what I want to no avail. Some examples of things I've tried are below. Anyone able to help me get further access without calling the variable directly with dot notation? I have also considered doing manual parsing of the JSON, but trying to avoid that. This is a react app, so if anyone can think of a library that could help, I'm all ears there as well.
//treating like a key
data.components.schemas['FakeAppConfiguration']
//trying to create a map
interface SchemaDef {
type: string,
properties: Properties,
//....etc,etc
}
let i = 0;
let schemas: Map<string, SchemaDef> = new Map<string, SchemaDef>();
for (let schema in data.components.schemas)
{
schemas.set(schema, data.components.schemas[i]);
i++;
}
You could iterate over the Object.entries() of your "schemas" object.
let schemas = {
"FakeAppConfiguration": {
"type": "object",
"properties": {
"setting1": {
"type": "string",
"nullable": true
},
"setting2": {
"type": "string",
"nullable": true
}
},
},
"FakeAppConfiguration2": {
"type": "object",
"properties": {
"setting1": {
"type": "string",
"nullable": true
},
"setting2": {
"type": "string",
"nullable": true
}
},
}
};
for (let [key, value] of Object.entries(schemas)) {
console.log(key, "\n\n", value);
}

How to get current user's data from loopback API?

I'm using loopback to create an API. So far I've been able to set up all the models that I require. How can we set up these endpoints so that they show the data for only the user that has been logged in?
For example, user A adds some data in the database and user B adds some other data in the database. Now if user A is logged in, I only want to get the data that was added by A. As of now I'm getting all the data present in the database together.
My model JSON is as follows:
{
"name": "IPs",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"identifier": {
"type": "string"
},
"IP": {
"type": "array"
},
"type": {
"type": "string"
},
"plugins": {
"type": "array"
}
},
"validations": [],
"relations": {},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$unauthenticated",
"permission": "DENY"
}
],
"methods": {}
}
and JS is as follows:
module.exports = function(IPs) {
};
Change principalId of the model from $unauthenticated to $owner
Note from loopback documentation:
To qualify a $owner, the target model needs to have a belongsTo relation to the User model (or a model that extends User) and property matching the foreign key of the target model instance. The check for $owner is performed only for a remote method that has ‘:id’ on the path, for example, GET /api/users/:id.

Customized schema resolving for invalid objects with AJV

I'm trying to find a way to alter schema validation to find most appropriate schema for a given object. Let's say we have a schema:
{
"oneOf": [
{
"$ref": "#/definitions/a"
},
{
"$ref": "#/definitions/b"
}
],
"definitions": {
"a": {
"type": "object",
"properties": {
"prop1": {
"enum": ["x"]
}
},
"required": ["prop1"]
},
"b": {
"type": "object",
"properties": {
"prop1": {
"enum": ["y"]
},
"prop2": {
"type": "string"
}
},
"required": ["prop1", "prop2"]
}
}
}
Now, if I have an object { "prop1": "y" }, I want it to be resolved as of #/definitions/b type, even if it's not really valid for this scheme. That is, I want to use just prop1 property for resolving.
I wonder if there is a way to do it using AJV custom keywords, without rebuilding the schema itself? In particular, if schema is not valid for an object, is it possible to use custom keywords to override it and make it valid?
If the objective is to only report errors from the correct schema you can use either "switch" (with v5 option, it is moved to ajv-keywords from version 5.0.0) or "if/then/else" (it is recommended as it is likely to be added in JSON Schema draft 7):
{
"id": "schema",
"if": { "properties": { "prop1": { "const": "x" } } },
"then": { "$ref": "#/definitions/a" },
"else": { "$ref": "#/definitions/b" }
}
If you need to know which schema was used for validation you can use a custom keyword to make a note of it:
{
"id": "schema",
"if": { "properties": { "prop1": { "const": "x" } } },
"then": {
"allOf": [
{ "schemaUsed": "schema#/definitions/a" },
{ "$ref": "#/definitions/a" }
]
},
"else": {
"allOf": [
{ "schemaUsed": "schema#/definitions/b" },
{ "$ref": "#/definitions/b" }
]
}
}
The keyword should be defined to store schema ID during validation in some variable so that after validation you could see which one was used.
If you need to get actual schema you can do:
var validate = ajv.getSchema('schema#/definitions/a'); // validating function
var schema = validate.schema; // schema as JSON

Mongoose - use a post method to create a new empty collection

Libraries in use: Express, Mongoose, Express-Restify-Mongoose
Problem: I am trying to figure out how to create a POST request that will provide the schema in the req.body. I want to simply create a new collection if it does not already exist and enforce that new schema.
when I use the following code:
app.use('/api/v1', function(req, res, next) {
if(req.path[0] === '/' && -1 === req.path.indexOf('/', 1) && req.method === 'POST') {
var collection_name = req.path.substr(1, req.path.length - 1).toLowerCase();
if(mongoose.modelNames().indexOf(collection_name) === -1) {
// only create if model does not exist
console.log(req.body);
var schema = new mongoose.Schema({}, { strict: false, collection: collection_name });
var model = mongoose.model(collection_name, schema);
restify.serve(app, model, { plural: false, name: collection_name });
}
}
next();
});
It also posts an empty document to that collection. If I change the code ever so slightly so that var schema uses the post's req.body to determine the schema the POST request does not go through:
var schema = new mongoose.Schema(req.body, { strict: false, collection: collection_name });
Where the req.body from the POST is:
{
"update_count": { "type": "String", "required": "false" },
"created_date": { "type": "String", "required": "false" },
"created_by": { "type": "String", "required": "false" },
"updated_date": { "type": "String", "required": "false" },
"updated_by": { "type": "String", "required": "false" }
}
Which returns an error and does not complete the POST request because it is also trying to use that same req.body to follow the schema I've just set AND it wants to use the req.body to enter into the document.
{
"message": "testcollection3 validation failed",
"name": "ValidationError",
"errors": {
"updated_by": {
"message": "Cast to String failed for value \"[object Object]\" at path \"updated_by\"",
"name": "CastError",
"kind": "String",
"value": {
"type": "String",
"required": "false"
},
"path": "updated_by"
},
..................................
How can I set the schema with my post and also prevent a document from being created?
As you've seen, Mongoose won't create a model's collection until it needs to save a document to it. However, you can create the collection explicitly using the Db#createCollection method from the native MongoDB driver that's accessible via mongoose.connection.db:
mongoose.connection.db.createCollection(collection_name, (err) => {...});
Mongoose seems to create a still missing collection any time it is needed for some data access operation. So for me, (using v4.5.9) this works:
mongoose.connection.db.findOne({}).then(...).catch(...);

Categories

Resources