This is my data structure with query string.
Data structure
{
"root_firebase_DB": {
"users": [{
"meUID": {
"name": "ME",
"rooms": [{
"room_id_01": true
}]
}
}, {
"friendUID": {
"name": "FRIEND",
"rooms": [{
"room_id_01": true
}]
}
}],
"rooms": [{
"room_id_01": {
"meUID": true,
"friendUID": true
}
}],
"messages": [{
"room_id_01": {
"message_id_01": {
"text": "Hello world",
"user": {
"_id": "meUID",
"name": "ME"
}
},
"message_id_02": {
"text": "Xin Chao",
"user": {
"_id": "friendUID",
"name": "FRIEND"
}
}
}
}]
}
}
I have meUID and friendUID, i want to find a room that contain both us.
The query bellow is fine, but i got .indexOn warning
const meUID = 'abc';
const friendUID = 'abc';
/**
* Find room key
* filter by meUID and friendUID
*
* THIS WAY I GOT WARNING .indexON
*
*/
firebase.database().ref()
.child('rooms')
.orderByChild(meUID)
.equalTo(true)
.on('child_added', snap => {
if (snap.hasChild(friendUID)) {
console.log('Got roomKEY', snap.key);
}
});
In general way, i get array of rooms by meUID and loop, each loop i go to filter as bellow to find Is this room have my friendUID or not, this way i have no warning with index, but seem waste time to loop and check all rooms.
/**
* THIS WAY NOT GET ANY WARNING
* BUT IS THERE OTHER GOOD WAY TO GO?
*/
const ref = firebase.database().ref();
const meUID = 'aaa';
const friendUID = 'bbb';
const messages = [];
ref.child(`users/${meUID}/rooms`).once('value', snap => {
const roomKey = null;
// loop all room that belong to me
snap.forEach(room => {
// with each room, i must going to check is this room belong to friendUID or not
ref.child(`users/${friendUID}/rooms/${room.key}`).once('value', friendRoomSnap => {
if (friendRoomSnap.val()) {
// found room that belong to friendUID too
roomKey = room.key;
}
});
if (roomKey != null) {
//if found that room, break the loop
return true;
}
});
// goto fetching all messages in this room
ref.child(`messages/${roomKey}`).on('child_added', messageSnap => {
messages.push(messageSnap.val());
});
});
//Too much stuff, is there any way better?
Please help, i am newbie.
Does my data structure get problem?
Related
I'm using GraphQL with Meteor and Pup v2, and I have a problem accessing the users data via a special ID provided to every user on signup, this ID will be used in a link (mysite.com/user/specialId) so other users can view the searched users account. Problem is, I can't get the data with the special ID, I can't get any data back if I don't pass in the users _id provided by MongoDB. Below I have a bunch of the code used:
Attempt 1
I tried to use a custom on-the-go way just to be able to at least access the data to see if it works (and then implement it correctly later)
const GET_USER_DETAILS = gql`
query user($userId: String) {
user(userId: $userId) {
userId
username
_id
}
}
`;
Here I export so I can get the data:
export default compose(
graphql(GET_USER_DETAILS, {
options: ({ match }) => ({
fetchPolicy: 'no-cache',
variables: {
// existing specialId for testing purposes, to be replaced with match.params.userId
userId: "J4xZzvvhBDSEufnBn",
},
}),
}),
)(PublicProfileView);
This returns a 400 error Network error: Response not successful: Received status code 400 error and after multiple attempts, I could not fix it, so I tried a different approach...
Attempt 2
I tried to go deep into the files and change the GraphQL. Created a new query:
query userById($userId: String) {
userById(userId: $userId) {
...UserAttributes
}
}
(Mentioned fragment)
fragment UserAttributes on User {
_id
name {
...
}
username
emailAddress
oAuthProvider
roles {
...
}
settings {
...
}
userId
}
Tried to add new item in API:
type Query {
...
userById(userId: String): User
...
}
Resolver:
resolvers: {
Query: {
...
userById: (parent, args) => {
// Assuming args equals an object like { _id: '123' };
return UserQueries.userById(args);
},
},
},
query.js, attempt 1:
userById: (parent) => queryUsers.find({ userId: parent.userId }, { sort: { createdAt: 1 } }).fetch()
Attempt 2:
userById: (parent, args, context) => {
return queryUsers({
userId: parent.userId,
});
},
And finally
Attempt 3
I tried to modify the get query
const getQueryModified = (options) => {
// console.log(options.userId)
try {
return options.userId
? { 'userId': options.userId }
: { userId: options.userId };
} catch (exception) {
throw new Error(`[queryUsers.getQuery] ${exception.message}`);
}
};
Here is the original query I tried to modify:
const getQuery = (options) => {
try {
return options.search
? {
_id: { $ne: options.currentUser._id },
$or: [
{ 'profile.name.first': options.search },
{ 'profile.name.last': options.search },
{ 'emails.address': options.search },
// { 'userId': options.search },
{ 'services.facebook.first_name': options.search },
{ 'services.facebook.last_name': options.search },
{ 'services.facebook.email': options.search },
],
}
: { _id: options.currentUser._id };
} catch (exception) {
throw new Error(`[queryUsers.getQuery] ${exception.message}`);
}
};
Unfortunately this was also unsuccessful, the best I get from these when executing the below query is null...
userById(userId: "J4xZzvvhBDSEufnBn"){
username
}
All I want is to get the user data from their userId and not their _id, but I can't seem to figure out how to do it
UPDATE
It seems that I can't get the bucket's reference correctly or use the bucket functions.
When I do
// Get the photos' bucket
const bucket = gcs.bucket("photos");
console.log("deleting the bucket");
// Delete bucket
bucket.delete();
I get the error too (but the bucket exists in my project)
Error: There is an account problem for the requested project.
at new ApiError (/workspace/node_modules/#google-cloud/common/build/src/util.js:58:15)
at Util.parseHttpRespBody (/workspace/node_modules/#google-cloud/common/build/src/util.js:193:38)
at Util.handleResp (/workspace/node_modules/#google-cloud/common/build/src/util.js:134:117)
at retryRequest (/workspace/node_modules/#google-cloud/common/build/src/util.js:432:22)
at onResponse (/workspace/node_modules/retry-request/index.js:206:7)
at /workspace/node_modules/teeny-request/build/src/index.js:233:13
at process._tickCallback (internal/process/next_tick.js:68:7)
PROBLEM
I am having this error:
{
"error": {
"code": 401,
"message": "There is an account problem for the requested project.",
"errors": [
{
"message": "There is an account problem for the requested project.",
"domain": "global",
"reason": "required",
"locationType": "header",
"location": "Authorization"
}
]
}
}
PassThrough {
Unhandled error { Error: There is an account problem for the requested project
at new ApiError (/workspace/node_modules/#google-cloud/common/build/src/util.js:58:15)
_readableState:
ReadableState {
at Util.parseHttpRespBody (/workspace/node_modules/#google-cloud/common/build/src/util.js:193:38)
...
What I am doing is just:
Get a bucket reference
Get a file from the bucket with the file's path
Get metadata and the signed url of the file.
My problem is the third step. When I call those functions, I don't receive any answer... no metadata, no url.
Here is the code:
const { Storage } = require("#google-cloud/storage");
const gsc = new Storage()
const serviceAccount = require("./serviceAccount.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://my_app_id.firebaseio.com",
storageBucket: "my_app_id.appspot.com",
});
exports.validateImageDimensions = functions
.region("us-central1")
// Increased memory, decreased timeout (compared to defaults)
.runWith({ memory: "2GB", timeoutSeconds: 120 })
.https.onCall(async (data, context) => {
// Get the image's owner
const owner = context.auth.token.uid;
// Get the image's info
const { id, description, location, tags, time } = data;
// Get the photos' bucket
const bucket = gcs.bucket("photos");
// Get the file's path
const filePath = bucket.name + "/" + id;
// Get the file
const file = bucket.file(filePath);
console.log(JSON.stringify(file));
// Check if the file is a jpeg image
const metadata = await file.getMetadata(); // <------- ERROR
console.log(JSON.stringify(metadata));
const isJpgImage = metadata[0].contentType === "image/jpeg";
console.log(`Is jpeg? ${isJpgImage}`);
// Get the file's signed urls
const signedUrls = await file.getSignedUrl({
action: "read",
expires: "01-01-2100",
});
// signedUrls[0] contains the file's public URL
const publicUrl = signedUrls[0];
console.log(`publicUrl ${publicUrl}`);
console.log(`Url: ${publicUrl}`);
The file exists in my storage... and when I console.log it I can see:
{
"_eventsCount": 0,
"_events": {},
"id": "photos%2Fecbb4a5a-aa11-451f-b8af-efd3e8464a59",
"baseUrl": "/o",
"metadata": {},
"acl": { "pathPrefix": "/acl", "owners": {}, "readers": {}, "writers": {} },
"methods": {
"exists": { "reqOpts": { "qs": {} } },
"get": { "reqOpts": { "qs": {} } },
"delete": { "reqOpts": { "qs": {} } },
"getMetadata": { "reqOpts": { "qs": {} } },
"setMetadata": { "reqOpts": { "qs": {} } }
},
"bucket": {
"_eventsCount": 0,
"baseUrl": "/b",
"storage": {
"baseUrl": "https://storage.googleapis.com/storage/v1",
"projectId": "{{projectId}}",
"authClient": {
"cachedCredential": null,
"scopes": [
"https://www.googleapis.com/auth/iam",
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/devstorage.full_control"
],
"jsonContent": null,
"_cachedProjectId": null
},
"globalInterceptors": [],
"acl": {
"OWNER_ROLE": "OWNER",
"READER_ROLE": "READER",
"WRITER_ROLE": "WRITER"
},
...
Any ideas? I have been googling for hours but no answer.
Also, my storage rules:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
function isSignedIn() {
return request.auth.uid != null;
}
match /photos/{photo} {
function hasValidSize() {
// Max. photo size = 30MB (For all dimensions)
return request.resource.size < 30 * 1024 * 1024;
}
function isImage() {
return request.resource.contentType.matches("image/.*");
}
allow read: if true;
allow write: if isSignedIn() && isImage() && hasValidSize();
}
}
}
This is the DynamoDB table structure I'm working on:
{
"userId": "99999999-9999-9999-9999-999999999999",
"userProfile": {
"email": "myemail#gmail.com",
"firstName": "1234124",
"lastName": "123423",
},
"masterCards": [
{
"cardId": 101000000000001,
"cardImage": "logo.png",
"cardName": "VipCard1",
"cardWallet": "0xFDB17d12057b6Fe8c8c425D2DB88d8475674567"
},
{
"cardId": 102000000000002,
"cardImage": "logo.png",
"cardName": "VipCard2",
"cardWallet": "0xFDB17d12057b6Fe8c8c425D2DB88d8183454345"
},
{
"cardId": 103000000000003,
"cardImage": "logo.png",
"cardName": "VipCard3",
"cardWallet": "0xFDB17d12057b6Fe8c8c425D2DB88d8184345345"
}
],
}
I'm trying to increase the cardId field by one for the first list item with this Lambda function:
const dynamoDB = new AWS.DynamoDB({region: 'eu-central-1', apiVersion:'2012-08-10'});
const counterId="99999999-9999-9999-9999-999999999999"
const params = {
TableName:"FidelityCardsUsers",
Key: {"userId":{"S":counterId}},
UpdateExpression:"ADD #masterCards[0].#cardId :increment",
ExpressionAttributeNames:{
"#masterCards": "masterCards",
"#cardId": "cardId"
},
ExpressionAttributeValues:{":increment": {"N": "1"}}
}
dynamoDB.updateItem(params, function(err, data) {
if (err) {
console.log('error getting counter from DynamDB: ',err)
callback(err);
} else {
callback(null,data)
}
})
In return I get only a new top-level attribute named "mastercards[0].cardId[0]" with a value number set to 1.
I have tried to increment In an array and its work fine with AWS.DynamoDB.DocumentClient()
Example :
var AWS = require("aws-sdk");
var docClient = new AWS.DynamoDB.DocumentClient();
let params = {
TableName:'tableName',
Key: {
'venueId': 'VENUE_002'
},
UpdateExpression: "ADD #walk.#coordinates[0] :increment",
ExpressionAttributeNames: {
'#walk': 'walk',
'#coordinates': 'coordinates'
},
ExpressionAttributeValues: {
':increment': 1 // This is from the client
},
ReturnValues: 'UPDATED_NEW'
};
docClient.update(params, function (err, data) {
if (err) {
console.log('failure:updateShuttleDirection:failed');
console.log(err);
} else {
console.log('success:updateShuttleDirection:complete');
console.log(data);
}
});
Sample Data:
"walk": {
"coordinates": [
10,
20
],
"type": "Point"
},
I have tried to increment 10 to 11 and its work fine
Reading the doc here, it seems that:
the ADD action can only be used on top-level attributes, not nested
attributes.
In GraphiQL at http://localhost:8080/graphiql, I'm using this query:
{
instant_message(fromID: "1"){
fromID
toID
msgText
}
}
I'm getting this response:
{
"data": {
"instant_message": {
"fromID": null,
"toID": null,
"msgText": null
}
},
"errors": [
{
"message": "Resolve function for \"instant_message.fromID\" returned undefined",
"locations": [
{
"line": 3,
"column": 5
}
]
},
{
"message": "Resolve function for \"instant_message.toID\" returned undefined",
"locations": [
{
"line": 4,
"column": 5
}
]
},
{
"message": "Resolve function for \"instant_message.msgText\" returned undefined",
"locations": [
{
"line": 5,
"column": 5
}
]
}
]
}
I tried to set up my system according to the examples found here:
https://medium.com/apollo-stack/tutorial-building-a-graphql-server-cddaa023c035#.s7vjgjkb7
Looking at that article, it doesn't seem to be necessary to set up individual resolvers for string fields, but I must be missing something.
What is the correct way to update my resolvers so as to return results from string fields? Example code would be greatly appreciated!
Thanks very much in advance to all for any thoughts or info.
CONNECTORS
import Sequelize from 'sequelize';
//SQL CONNECTORS
const db = new Sequelize(Meteor.settings.postgres.current_dev_system.dbname, Meteor.settings.postgres.current_dev_system.dbuser, Meteor.settings.postgres.current_dev_system.dbpsd, {
host: 'localhost',
dialect: 'postgres',
});
db
.authenticate()
.then(function(err) {
console.log('Connection to Sequelize has been established successfully.');
})
.catch(function (err) {
console.log('Unable to connect to the Sequelize database:', err);
});
const IMModel = db.define('IM', {
id: {type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true},
fromID: {type: Sequelize.STRING},
toID: {type: Sequelize.STRING},
msgText: {type: Sequelize.STRING}
});
IMModel.sync({force: true}).then(function () {
// Table created
return IMModel.create({
fromID: '1',
toID: '2',
msgText: 'msg set up via IMModel.create'
});
});
const IM = db.models.IM;
export {db, IM };
SCHEMA
const typeDefinitions = [`
type instant_message {
id: Int
fromID: String
toID: String
msgText: String
}
type Query {
instant_message(fromID: String, toID: String, msgText: String): instant_message
}
type RootMutation {
createInstant_message(
fromID: String!
toID: String!
msgText: String!
): instant_message
}
schema {
query: Query,
mutation: RootMutation
}
`];
export default typeDefinitions;
RESOLVERS
import * as connectors from './db-connectors';
import { Kind } from 'graphql/language';
const b = 100;
const resolvers = {
Query: {
instant_message(_, args) {
const a = 100;
return connectors.IM.find({ where: args });
}
},
RootMutation: {
createInstant_message: (__, args) => { return connectors.IM.create(args); },
},
};
export default resolvers;
When you define your GraphQLObjectTypes you need to provide a resolver for each of their fields.
You defined your instant_message with multiple fields but did not provide resolvers for each of these fields.
More over you defined the types of those field with regular typescript fields while you need to define it with GraphQL types (GraphQLInt, GraphQLString, GrapQLFloat etc..)
So defining your type should look something like this:
let instant_message = new GraphQLObjectType({
id: {
type: GraphQLInt,
resolve: (instantMsg)=> {return instantMsg.id}
}
fromID: {
type: GraphQLString,
resolve: (instantMsg)=> {return instantMsg.fromID}
}
toID: {
type: GraphQLString,
resolve: (instantMsg)=> {return instantMsg.toID}
}
msgText: {
type: GraphQLString,
resolve: (instantMsg)=> {return instantMsg.msgText}
}
})
In addition, you will need to define your Query as follows:
let Query = new GraphQLObjectType({
name: "query",
description: "...",
fields: () => ({
instant_messages: {
type: new GraphQLList(instant_message),
args: {
id: {type: GraphQLInt}
},
resolve: (root, args) => {
connectors.IM.find({ where: args })
}
}
})
})
The issue is that the query does not expect an array,
Please fix it:
type Query {
instant_message(fromID: String, toID: String, msgText: String): [instant_message]
}
Then you should make sure the resolver returns Array of objects, if it doesnt work then the resolver is not returning an Array.
I have an extended question to this question.
What if the player belong to more than one team?
I have this
json
"Players" : {
"-YRHd4IjrjsBXx__B" : {
"name" : "The best forward",
"creationDate" : "2016-02-26 15:50:39",
"teams" : {
"-KAByPeIz4IjrjsBXx__B" : true,
"-KEFPuCXcqOah_GJwsMCu" : true,
"-KEwuQxvGpYTEJ7YQ58-l" : true,
"-KKF8vPtf8J7cfqFh2PLm" : true
},
},
etc...
}
players-service.js
getPlayers: function(teamid) {
var Players = {};
var teamsIndex = ref.child('teams/' + teamid + '/players/');
var playersIndex = ref.child('players/');
teamsIndex.on('child_added', function(snapshot) {
var playerKey = snapshot.key;
playersIndex.child(playerKey).on('value', function(playersnap){
$timeout(function() {
console.log("key", playerKey);
players[playerKey] = playersnap.val();
});
});
});
teamIndex.on('child_removed', function(snapshot) {
$timeout(function(snapshot) {
delete players[snapshot.key()];
});
});
return players;
}
But it returns a list of object. I know that I could probably query/change the data structure to/in firebase and return it as a $firebaseArray which I prefer as I use angularfire.
You usually structure your data depending on how you want to retrieve them.
From my understanding (correct me if I'm wrong) you want to get all the players in a team. For this purpose I would use this structure:
"Players": {
"player1": {...},
"player2": {...},
"player3": {...}
},
"Teams': {
"team1": {...},
"team2": {...}
},
"TeamPlayers" : {
"team1": {
"player1": true,
"player2": true
},
"team2": {
"player1": true,
"player3": true
}
}
Or using an array
"TeamPlayers" : {
"team1": [
0: "player1",
1: "player2"
]
}