sails.sockets.rooms() and sails.sockets.socketRooms() are both deprecated
In the doc:
This method is deprecated. Please keep track of your rooms in app-level code.
I am new to SailsJS.
Question 1: How could I get started with that? Where should I declare my room array or hash?
Question 2: How do I get the list of the clients connected?
Here the way I did it. Hope this helps a little, although I am not confident it's perfect, BUT it worked for me. Feel free to suggest corrections, enhancements.
I declared a sails.rooms = {}; in the controller (Question 1) where I needed it most. (You can name it whatever you want.)
sails. gives it access across the application.
In the controller
/**
* Socket connection
*/
connect: function(req, res) {
// Check if it's socket connection
if (!req.isSocket) { return res.badRequest(); }
// label the room to ROOM_NAME
sails.sockets.join(req, ROOM_NAME);
/**
* Manage my `sails.rooms` HERE
* e.g.
*/
if (!sails.rooms[ROOM_NAME]) {
sails.rooms[ROOM_NAME] = {};
}
sails.rooms[ROOM_NAME][req.socket.id] = HUMAN_FRIENDLY_LABEL;
}
I end up with a JSON that looks like this:
{
{ "room1" :
{
"ksadj1234clkjnelckjna" : "john",
"eroiucnw934cneo3vra09" : "marie"
}
},
{ "room2" :
{
"kslaksdjfnasjdkfa9jna" : "antoine",
"qweuiqcnw934cne3vra09" : "michelle"
}
}
}
In config/sockets.js, I manage disconnections
afterDisconnect: function(session, socket, cb) {
console.log(socket.id + " disconnected");
/**
* Manage sails.rooms HERE
* e.g.
*/
for (r in sails.rooms) {
delete sails.rooms[r][socket.id];
//
// YOUR CODE
//
}
return cb();
},
NOTE: that you can list the WebSockets in a certain room using sails.io.sockets.in(ROOM_NAME). This should also help to purge the sails.rooms from disconnected sockets.
Related
This is a pretty simple question.
How to implement subscriptions in graphql?
I'm asking specifically for when using graphql.js constructors like below ?
I could not find a clean/simple implementation.
There is another question here, but it deals with relay.js - i don't want to unnecessarily increase the nr of external dependencies in my app.
What i have:
module.exports = function (database){
return new GraphQLSchema(
{ query: RootQuery(database)
, mutation: RootMutation(database)
, subscription: RootSubscription(database) -- i can see this in graphiql - see below
}
);
}
function RootSubscription(database){
return new GraphQLObjectType(
{ name: "RootSubscriptionType"
, fields:
{ getCounterEvery2Seconds:
{ type: new GraphQLNonNull(GraphQLInt)
, args :
{ id: { type: GraphQLString }
}
, subscribe(parent, args, context){
// this subscribe function is never called .. why?
const iterator = simpleIterator()
return iterator
}
}
}
}
)
}
I learned that i need a subscribe() which must return an iterator from this github issue.
And here is a simple async iterator. All this iterator does - is to increase and return the counter every 2 seconds. When it reaches 10 it stops.
function simpleIterator(){
return {
[ Symbol.asyncIterator ]: () => {
let i = 0
return {
next: async function(){
i++
await delay(2000)
if(i > 10){
return { done: true }
}
return {
value: i,
done: false
}
}
}
}
}
}
When i run the graphiql subscription, it returns null for some reason:
I'm piecing together code from multiple sources - wasting time and hacking it basically. Can you help me figure this one out?
Subscriptions are such a big feature, where are they properly documented? Where is that snippet of code which you just copy paste - like queries are for example - look here.
Also, i can't use an example where the schema is separate - as a string/from a file. I already created my schema as javascript constructors. Now since im trying to add subscriptions i can't just move back to using a schema as a string. Requires rewriting the entire project. Or can i actually have both? Thanks :)
I'm trying to build a query with the GitHub API v4 (GraphQL) to get the number of contributors.
At the moment I have something of the likes of
query ($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
ref(qualifiedName: "master") {
target {
... on Commit {
history(first: 100) {
nodes {
author {
name
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
}
}
where I'm going through all the commits and get the name of the Authors (at the time I was trying to get the number of commits for contributor), but for repositories with a large amount of commits, this takes a lot of time.
So back to my question, is the a way to get only the number of contributors in a repository?
As far as I know, this is only possible with the REST API (v3), by requesting only one item per page, and extracting the total number of pages in the Response headers.
With this graphql query you can get the:
- total releases count
- total branches count
- total commits count
query{
repository(owner:"kasadawa", name:"vmware-task") {
Releases:refs(first: 0, refPrefix: "refs/tags/") {
totalCount
}
Branches:refs(first: 0, refPrefix: "refs/heads/") {
totalCount
}
object(expression:"master") {
... on Commit {
history {
totalCount
}
}
}
}
}
But if you want to get the contributors, you should do it with the REST API, because currently there is no simple way to implement it with GRAPHQL API.
Here is a solutions with the REST API.
const options = {
url: 'https://api.github.com/repos/vmware/contributors' ,
'json': true ,
headers: {
'User-Agent':'request',
'Authorization': 'token API_KEY_GENERATED_FROM_GITHUB'
}
};
var lastPageNum = 0 ; // global variable
request.getAsync(options).then((res) =>{
this.checkHeaders(res.headers.status) // just check the header status
// check if there are more pages
if(!!res.headers.link){
const [ , lastURL] = res.headers.link.split(','); // getting the last page link from the header
lastPageNum = +lastURL.match(/page=(\d+)/)[1]; // get the number from the url string
options.url = licenseUrl + lastPageNum;
return request.getAsync(options) // make Request with the last page, in order to get the last page results, they could be less than 30
}else{
// if its single page just resolve on to the chain
return Promise.resolve(res.body.length);
}
})
.then((lastPageRes)=>{
return (lastPageNum !== 0
? ( (lastPageNum-1)*30 + lastPageRes.body.length )
: lastPageRes)
})
.catch() // handle errors
Checkout for updates: https://github.community/t5/GitHub-API-Development-and/Get-contributor-count-with-the-graphql-api/td-p/18593
I was going through the relay docs and came to following code in RANGE_ADD.
class IntroduceShipMutation extends Relay.Mutation {
// This mutation declares a dependency on the faction
// into which this ship is to be introduced.
static fragments = {
faction: () => Relay.QL`fragment on Faction { id }`,
};
// Introducing a ship will add it to a faction's fleet, so we
// specify the faction's ships connection as part of the fat query.
getFatQuery() {
return Relay.QL`
fragment on IntroduceShipPayload {
faction { ships },
newShipEdge,
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'faction',
parentID: this.props.faction.id,
connectionName: 'ships',
edgeName: 'newShipEdge',
rangeBehaviors: {
// When the ships connection is not under the influence
// of any call, append the ship to the end of the connection
'': 'append',
// Prepend the ship, wherever the connection is sorted by age
'orderby(newest)': 'prepend',
},
}];
}
/* ... */
}
Now over here it is mentioned that edgeName is required for adding new node to the connection. Looks well and fine.
Now, I move further down the documentation and reached the GraphQL implementation of this mutation.
mutation AddBWingQuery($input: IntroduceShipInput!) {
introduceShip(input: $input) {
ship {
id
name
}
faction {
name
}
clientMutationId
}
}
Now according to docs this mutation gives me output as
{
"introduceShip": {
"ship": {
"id": "U2hpcDo5",
"name": "B-Wing"
},
"faction": {
"name": "Alliance to Restore the Republic"
},
"clientMutationId": "abcde"
}
}
I cannot see edgeName being present here.
I was using graphene for my project. Over there also I saw something similar only
class IntroduceShip(relay.ClientIDMutation):
class Input:
ship_name = graphene.String(required=True)
faction_id = graphene.String(required=True)
ship = graphene.Field(Ship)
faction = graphene.Field(Faction)
#classmethod
def mutate_and_get_payload(cls, input, context, info):
ship_name = input.get('ship_name')
faction_id = input.get('faction_id')
ship = create_ship(ship_name, faction_id)
faction = get_faction(faction_id)
return IntroduceShip(ship=ship, faction=faction)
Over here also I cannot see edgeName anywhere.
Any help please? I am working on mutations for the first so wanted to confirm a m I missing something or is something wrong here?
This example might be either simplified or a bit obsoloete, because in practice there is need to return edge and that's exactly what is fetched by relay (other fields in RANGE_ADD are more a kind of declaration and are not necessarily fetched).
Here is how you can do it in graphene:
# Import valid as of graphene==0.10.2 and graphql-relay=0.4.4
from graphql_relay.connection.arrayconnection import offset_to_cursor
class IntroduceShip(relay.ClientIDMutation):
class Input:
ship_name = graphene.String(required=True)
faction_id = graphene.String(required=True)
ship = graphene.Field(Ship)
faction = graphene.Field(Faction)
new_ship_edge = graphene.Field(Ship.get_edge_type().for_node(Ship))
#classmethod
def mutate_and_get_payload(cls, input, context, info):
ship_name = input.get('ship_name')
faction_id = input.get('faction_id')
ship = create_ship(ship_name, faction_id)
faction = get_faction(faction_id)
ship_edge_type = Ship.get_edge_type().for_node(Ship)
new_ship_edge = edge_type(
# Assuming get_ships_number supplied
cursor=offset_to_cursor(get_ships_number())
node=ship
)
return cls(ship=ship, faction=faction, new_ship_edge=new_ship_edge)
very simple question: if I try to validate a password in a User model it seems I can only validate the already encrypted password?
So for example if I use
Customer.validatesLengthOf('password', { min: 8, message: 'Too short' })
Then the encrypted password is checked (which is always longer than 8 characters), so no good... If I try to use a custom validation, how can I get access to the original password (the original req.body.password basically)?
EDIT (August 20, 2019): I am unsure if this is still an issue in the latest loopback releases.
In fact, this is a known problem in loopback. The tacitly approved solution is to override the <UserModel>.validatePassword() method with your own. YMMV.
akapaul commented on Jan 10, 2017 •
I've found another way to do this. In common model User there is a
method called validatePassword. If we extend our UserModel from User,
we can redefine this method in JS, like following:
var g = require('loopback/lib/globalize');
module.exports = function(UserModel) {
UserModel.validatePassword = function(plain) {
var err,
passwordProperties = UserModel.definition.properties.password;
if (plain.length > passwordProperties.max) {
err = new Error (g.f('Password too long: %s (maximum %d symbols)', plain, passwordProperties.max));
err.code = 'PASSWORD_TOO_LONG';
} else if (plain.length < passwordProperties.min) {
err = new Error(g.f('Password too short: %s (minimum %d symbols)', plain, passwordProperties.min));
err.code = 'PASSWORD_TOO_SHORT';
} else if(!(new RegExp(passwordProperties.pattern, 'g').test(plain))) {
err = new Error(g.f('Invalid password: %s (symbols and numbers are allowed)', plain));
err.code = 'INVALID_PASSWORD';
} else {
return true;
}
err.statusCode = 422;
throw err;
};
};
This works for me. I don't think that g (globalize) object is required
here, but I added this, just in case. Also, I've added my validator
options in JSON definition of UserModel, because of Loopback docs
For using the above code, one would put their validation rules in the model's .json definition like so (see max, min, and pattern under properties.password):
{
"name": "UserModel",
"base": "User",
...
"properties": {
...
"password": {
"type": "string",
"required": true,
...
"max": 50,
"min": 8,
"pattern": "(?=.*[A-Z])(?=.*[!##$&*])(?=.*[0-9])(?=.*[a-z])^.*$"
},
...
},
...
}
ok, no answer so what I'm doing is using a remote hook to get access to the original plain password and that'll do for now.
var plainPwd
Customer.beforeRemote( 'create', function (ctx, inst, next) {
plainPwd = ctx.req.body.password
next()
})
Then I can use it in a custom validation:
Customer.validate( 'password', function (err, res) {
const pattern = new RegExp(/some-regex/)
if (plainPwd && ! pattern.test( plainPwd )) err()
}, { message: 'Invalid format' })
Ok I guess the above answer is quite novel and obviously is accepted, but If you want a real easy solution with just some basic validations done and not much code then loopback-mixin-complexity is the solution for you.
If you don't want to create another dependency then you can go ahead with a custom mixin, that you can add into your user model or any other model where you need some kind of validation and it would do the validation for you.
Here's a sample code for how to create such mixin
module.exports = function(Model, options) {
'use strict';
Model.observe('before save', function event(ctx, next) { //Observe any insert/update event on Model
if (ctx.instance) {
if(!yourValidatorFn(ctx.instance.password) )
next('password not valid');
else
next();
}
else {
if(!yourValidatorFn(ctx.data.password) )
next('password not valid');
else
next();
}
});
};
I'm working on an app with Meteor and React.
I'm trying to have the content of inventories_array from an external MongoDB database, but it's extremely slow. I wait 7 seconds, I have one object, I wait 5 seconds, two other objects, etc...
Spaces = new Mongo.Collection("Space");
Properties = new Mongo.Collection("Property");
Inventories = new Mongo.Collection("Inventory");
if (Meteor.isClient) {
Meteor.subscribe("Property");
Meteor.subscribe("Space");
Meteor.subscribe("Inventory");
Tracker.autorun(function() {
inventories_array = Inventories.find({quantityBooked: 2},{fields: {priceTaxExcl: 1, property: 1, space: 1}}).fetch();
console.log(inventories_array);
}
if (Meteor.isServer) {
Meteor.publish("Property", function () {
return Properties.find();
});
Meteor.publish("Space", function () {
return Spaces.find();
});
Meteor.publish("Inventory", function () {
return Inventories.find();
});
}
The Inventory Object:
{
...
"property" : ObjectId("..."),
"space" : ObjectId("..."),
"quantityBooked":2,
"priceTaxExcl":...,
...
}
I launch the app with MONGO_URL=mongodb://127.0.0.1:27017/mydb meteor run
Any ideas why it's so slow?
If you look at your network tab in the inspector you'll see all the data flowing from the server to the client for each subscription and you'll be able to judge both how large it is and how long it takes.
I'd recommend at a first step that you alter your Inventory publication as follows:
Meteor.publish("Inventory", function () {
if ( this.userId ){ // only return data if we have a logged-in user
return Inventories.find({quantityBooked: 2},{fields: {priceTaxExcl: 1, property: 1, space: 1}});
} else {
this.ready();
}
});
This way your server is only sending the required fields from the required documents (assuming you only want docs where {quantityBooked: 2}.