Rooms/Channels and userId in Meteor.js - javascript

I'm building a multiplayer, turn-based game using meteor.js. The application will handle multiple games, so I'd like to separate my users into rooms.
I've done it before using socket.io channels, but I'm struggling to understand how it should be done in Meteor.
The flow I'd like to achieve is:
User visits http://localhost:3000/join/userId
I make a server-side call to an external API using "sessionId" as parameter, getting user's userId, his assigned roomId and an array of allowed userId's for this room
I'd like to create a room with roomId for the user or join him to an existing one. I know I should create a 'Rooms' collection, but I don't know how to tie users to my rooms and publish messages only to those present in the given room.
I'd like to avoid using 'accounts' package, because I don't need authorisation on my side - it'll be handled by step #2 mentioned above - but if the easiest and cleanest way of doing it involves adding this package, I can change my mind.

Your Rooms collection could look like:
{
_id: "<auto-generated>",
roomId: "roomId",
users: [ "user1", "user2", "user3", ... ],
messages: [
{ message: "", userId: "" },
{ message: "", userId: "" },
{ message: "", userId: "" },
...
]
}
The server-side API call returns
userId and roomId among other information.
So you can do a
Rooms.update({ roomId: roomId }, { $push: { users: userId } }, { upsert: true });
This would push the user into the exiting room or create a new room and add the user.
Your publish function could look like:
Meteor.publish("room", function(roomId) {
// Since you are not using accounts package, you will have to get the userId using the sessionId that you've specified or some other way.
// Let us assume your function getUserId does just that.
userId: getUserId( sessionId );
return Rooms.find({ roomId: roomId, users: userId });
// Only the room's users will get the data now.
});
Hope this helps.

Related

Disconnecting Many-to-Many Relationships in Prisma + MySQL

I am so completely lost. I have an explicit many to many relation: Users can have multiple Lists, but lists can be owned by multiple users:
model List {
id String #id #default(cuid())
title String
users UsersOnLists[]
}
model User {
id String #id #default(cuid())
name String
lists UsersOnLists[]
}
model UsersOnLists {
id String #id #default(cuid())
order Int
user DictItem? #relation(fields: [userId], references: [id])
userId String?
list List? #relation(fields: [ListId], references: [id])
listId String?
}
Now I'd like to connect a list to a user:
prisma.list.update({
where: {
id: input.id
},
data: {
users: {
create: [{
order: 123,
user: {
connect: {
id: "abcd-123",
}
}
}],
}
}
});
This works.
However, I don't know how to go about disconnecting many-to-many relations in prisma? Say I want to disconnect the user again from the list? How would I do this?
prisma.list.update({
where: {
id: input.id
},
data: {
users: {
disconnect: [{
user: {
disconnect: {
id: "abcd-123",
}
}
}],
}
}
});
This doesn't work.
I also can't find much in the prisma docs about disconnecting. Any ideas?
I guess I could jus delete the row from the Relations-Table, but this doesn't feel as clean and I guess I would still have the old ids in the user & list tables? I would prefer using disconnect, if this is the recommended method for that.
Are you getting a specific error? If you are using a code editor/IDE with TypeScript hinting, it should be giving you a specific error(s) about what's going on. If not that, then the command line should be giving you errors when you attempt to run an operation.
Docs: https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#disconnect-a-related-record
The "disconnect" operation cannot disconnect deeply-nested relations. It only disconnects documents directly connected to the model in question. In your situation, you can only disconnect a UserOnList from a List, but you cannot also disconnect User from UserOnList in the same operation.
prisma.list.update({
where: {
id: input.id
},
data: {
users: {
disconnect: [{
id: "ID_OF_UsersInList_MODEL_HERE"
}],
}
}
});
Also - you don't need the UsersInList table. Prisma can manage the "join" table under the hood for you if you don't need any extra information or data on that model. Check out the docs here if you want Prisma to manage this table on its own: https://www.prisma.io/docs/concepts/components/prisma-schema/relations/many-to-many-relations

Firebase data structure for one to chat app or private

Currently I am developing a web applications where i need to add chat functionality. A user publish his product, and another user interested can chat with the seller to stay or know more details about product.
I got stuck on implantation of chat, I can't find out a suitable data structure for need.
My need is; let's say John is the seller and publish his phone to sell. Charles got interest on John phone's and he want to know more details about that, so start chatting with John. They don't know each other before chatting, not like WhatsApp. Where a user know another user before send a message.
John could have published so many products and Charles could interest on different products of him, for each product will create a new conversation (chat).
I want that, if john delete a message, then that should delete from his chat not from Charles.
The chat is private or one to one.
Until now i have done this data structure. I don't know if it is the best way? please suggest me
chats
sender_ID _ Reciever_id
product_id
message:''
Timestamp:''
SenderName:''
When a user publishes a product (the potential seller), you'll need to associate their user ID with that product. Based on that, the interested user (potential buyer) can make the connection.
Given your other requirements, I'd nest the chats:
chats: {
uid1_uid2_productid: {
pushid: { message: ..., timestamp: ..., sender: ... },
pushid: { message: ..., timestamp: ..., sender: ... }
}
}
And then associate these chats with the correct users in user-specific lists:
user_chats: {
uid1: {
uid1_uid2_productid: true
},
uid2: {
uid1_uid2_productid: true
}
}
Instead of true you could also store a value (or more properties) that help you display the list of chats for that specific user.
If you want each user to have a completely separate copy of the chat/room, your easiest approach is to duplicate the rooms. So:
chats: {
uid1_uid2_productid: {
pushid: { message: ..., timestamp: ..., sender: ... },
pushid: { message: ..., timestamp: ..., sender: ... }
}
uid2_uid1_productid: {
pushid: { message: ..., timestamp: ..., sender: ... },
pushid: { message: ..., timestamp: ..., sender: ... }
}
}
And then:
user_chats: {
uid1: {
uid1_uid2_productid: true
},
uid2: {
uid2_uid1_productid: true
}
}

Access CosmosDB from different bots

I'm trying to read and write from/to an Azure Cosmos DB with two different bots (js, v4, ms botframework).
Chatbot 1:
- Chat with user, save user data and use it later
Chatbot 2:
- Read and display some user data
I use the following client: https://github.com/Microsoft/BotFramework-WebChat
Scenario:
I fixate my userID in the client (which has a directline to bot 1) to let's say "123"
I use Bot 1 and enter my username in the dialog (prompted by bot)
I refresh the Website on which Bot 1 is running with the same id "123"
I see that the bot still has my data stored
I change the ID in my client to "124"
I use Bot 1 and see there is no stored data (which is expected since ID "124" has never chatted with Bot 1)
I change the ID back to "123"
I use bot 1 and see that data from step 2 is still there
I use bot 2 with the id "123"
I see that there is no data ("undefined")
I use bot with ID "123" again
I see that the data from step 2 is gone
Which means that whenever I access the database with my second bot it seems like the data is cleared / deleted.
This is how I access the DB in index.js:
//Add CosmosDB (info in .env file)
const memoryStorage = new CosmosDbStorage({
serviceEndpoint: process.env.ACTUAL_SERVICE_ENDPOINT,
authKey: process.env.ACTUAL_AUTH_KEY,
databaseId: process.env.DATABASE,
collectionId: process.env.COLLECTION
})
// ConversationState and UserState
const conversationState = new ConversationState(memoryStorage);
const userState = new UserState(memoryStorage);
// Use middleware to write/read from DB
adapter.use(new AutoSaveStateMiddleware(conversationState));
adapter.use(new AutoSaveStateMiddleware(userState));
This is how I use the DB in bot.js:
constructor(conversationState, userState, dialogSet, memoryStorage) {
// Creates a new state accessor property.
// See https://aka.ms/about-bot-state-accessors to learn more about the bot state and state accessors
this.conversationState = conversationState;
this.userState = userState;
// Memory storage
this.memoryStorage = memoryStorage;
// Conversation Data Property for ConversationState
this.conversationData = conversationState.createProperty(CONVERSATION_DATA_PROPERTY);
// Properties for UserState
this.userData = userState.createProperty(USER_DATA_PROPERTY);
this.investmentData = userState.createProperty(INVESTMENT_DATA_PROPERTY);
}
async displayPayout (step) {
console.log("Display Payout");
// Retrieve user object from UserState storage
const userInvestData = await this.investmentData.get(step.context, {});
const user = await this.userData.get(step.context, {});
await step.context.sendActivity(`Hallo ${user.name}. Am Ausgang kannst du dir deine Bezahlung von ${userInvestData.payout} abholen.` );
}
The code snipped is from bot 2. Bot 1 saves the data in the same way. You can find the repos here:
Bot 1: https://github.com/FRANZKAFKA13/roboadvisoryBot
Bot 2: https://github.com/FRANZKAFKA13/displayBot
Client for Bot 1: https://github.com/FRANZKAFKA13/ra-bot-website-c
Client for Bot 2: https://github.com/FRANZKAFKA13/ra-bot-website-display
I also tried to use the "readOnly" key from CosmosDB in bot 2, which throws an error:
[onTurnError]: [object Object]
(node:1640) UnhandledPromiseRejectionWarning: TypeError: Cannot perform 'set' on a proxy that has been revoked
at adapter.sendActivities.then (C:\Users\X\Implementierung\display_bot\node_modules\botbuilder-core\lib\turnContext.js:175:36)
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
(node:1640) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 3)
Another behavior that I have noticed: When I trigger a "join event" through my client with a redux store, the userdata is not saved as well (every time I refresh the page, the data is gone, despite using the same id "123" all the time)
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
// Event starting bot's conversation
name: 'webchat/join',
value: {}
}
Any ideas? Thanks in advance
Since storage ids (see image) are created automatically based off of user ids (that may also be created automatically and varies by channel) and channel ids, this can be very difficult to do. It can make it very difficult to persist user and conversation data, particularly across bots and channels.
Example ID:
Here's more on how IDs work.
Personally, I would write my own, custom storage, instead of (or in addition to) saving it with UserState.
To write your data, do something like this:
const changes = {};
const userDataToWrite = {
name: step.result,
eTag: '*',
}
// Replace 'UserId' with however you want to set the UserId
changes['UserId'] = userDataToWrite;
this.memoryStorage.write(changes);
This will store a document that looks like this (I set 'UserId' to 'user123':
To read:
const userDataFromStorage = await this.memoryStorage.read(['UserId']);
userDataFromStorage will look like this:
{ UserId:
{ name: 'myName',
eTag: '"0000c700-0000-0000-0000-5c7879d30000"' } }
You'll have to manage userIds yourself, but this will ensure that the data can be read across bots, channels, and users.
Edit: Solved it by adding "[this.userID]" after each "user" call.
I tried your method and whenever I write the data, a new eTag is created which leads to the object being split apart:
"document": {
"25781dc4-805d-4e69-bf89-da1f4d72e7cb": {
"25781dc4-805d-4e69-bf89-da1f4d72e7cb": {
"25781dc4-805d-4e69-bf89-da1f4d72e7cb": {
"25781dc4-805d-4e69-bf89-da1f4d72e7cb": {
"25781dc4-805d-4e69-bf89-da1f4d72e7cb": {
"name": "",
"age": "",
"gender": "",
"education": "",
"major": "",
"eTag": "\"00003998-0000-0000-0000-5c797fff0000\""
},
"name": "Jane Doe",
"eTag": "\"00003e98-0000-0000-0000-5c7980080000\""
},
"age": 22,
"eTag": "\"00004898-0000-0000-0000-5c7980150000\""
},
"gender": "female",
"eTag": "\"00004d98-0000-0000-0000-5c79801b0000\""
},
"education": "Bachelor",
"eTag": "\"00005498-0000-0000-0000-5c7980200000\""
},
"major": "Business Administration",
"complete": true
How can I prevent this?
My Code:
In Constructor:
this.changes = {};
this.userID = "";
this.userDatax = {
name: "",
age: "",
gender: "",
education: "",
major: "",
eTag: '*',
}
In Dialogs:
async welcomeUser (step) {
console.log("Welcome User Dialog");
//step.context.sendActivity({ type: ActivityTypes.Typing});
// Initialize UserData Object and save it to DB
this.changes[this.userID] = this.userDatax;
await this.memoryStorage.write(this.changes);
}
async promptForAge (step) {
console.log("Age Prompt");
// Read UserData from DB
var user = await this.memoryStorage.read([this.userID]);
console.log(user);
// Before saving entry, check if it already exists
if(!user.name) {
user.name = step.result;
user.eTag = '*';
// Write userData to DB
this.changes[this.userID] = user;
await this.memoryStorage.write(this.changes);
}
}

Firebase nested queries slow - sendRequest call when we're not connected not allowed

I want to implement a follow system between users.
For that, I want to display all of the 250 users of my app, then add a checkmark button next to the ones I already follow, and an empty button next to the ones I do not follow.
var usersRef = firebase.database().ref(‘/users’);
var followingRef = firebase.database().ref(‘/followingByUser’);
var displayedUsers = [];
// I loop through all users of my app
usersRef.once('value', users => {
users.forEach(user => {
// For each user, I check if I already follow him or not
followingRef.child(myUid).child(user.key).once('value', follow => {
if (follow.val()) {
// I do follow this user, follow button is on
displayedUsers.push({
name: user.val().name,
following: true
});
} else {
// I do not follow this user, follow button is off
displayedUsers.push({
name: user.val().name,
following: false
});
}
})
})
})
When doing that, I often (not always) get the following error: "Error: Firebase Database (4.1.3) INTERNAL ASSERT FAILED: sendRequest call when we're not connected not allowed."

Eventually, all the data is fetched, but after 10 seconds instead of 1 (without the error).
I do not believe it is an internet connection issue, as I have a very fast and stable wifi.
Is it a bad practice to nest queries like that?
If not, why do I get this error?
My data is structured as below:
users: {
userId1: {
name: User 1,
email: email#exemple.com,
avatar: url.com
},
userId2: {
name: User 2,
email: email#exemple.com,
avatar: url.com
},
...
}
followByUser: {
userId1: {
userId2: true,
userId10: true,
userId223: true
},
userId2: {
userId23: true,
userId100: true,
userId203: true
},
...
}
Your current database structure allows you to efficiently look up who each user is following. As you've found out it does not allow you to look who a user is follow by. If you also want to allow an efficient lookup of the latter, you should add additional data to your model:
followedByUser: {
userId2: {
userId1: true,
}
userId10: {
userId1: true,
},
userId223: {
userId1: true,
},
...
}
This is a quite common pattern in Firebase and other NoSQL databases: you often expand your data model to allow the use-cases that your app needs.
Also see my explanation on modeling many-to-many relations and the AskFirebase video on the same topic.

How do I link each user to their data in Firebase?

I plan to create a main tree named users which will include the name different users used as username. So, from each username will be included their data e.g. Full Name, Address, Phone No.
I want to know how to get each user's data when they log in on their profile.
First of all i suggest you spend some time getting familiar with firebase by reading the Firebase Guide (Link to old Firebase Guide). Everything you need to know to answer your own question is available there. But for simplicity i will put an example here:
Lets start with security, here are the basic firebase rules you need for this example: (source: Understanding Security) (old source: Understanding Security)
{
"rules": {
"users": {
"$user_id": {
".write": "$user_id === auth.uid"
}
}
}
}
I will skip the actual user creation and logging in and focus on the question about storing and retrieving user data.
Storing data: (source: Firebase Authentication) (old source: User Authentication)
// Get a reference to the database service
var database = firebase.database();
// save the user's profile into Firebase so we can list users,
// use them in Security and Firebase Rules, and show profiles
function writeUserData(userId, name, email, imageUrl) {
firebase.database().ref('users/' + userId).set({
username: name,
email: email
//some more user data
});
}
The resulting firebase data will look like this:
{
"users": {
"simplelogin:213": {
"username": "password",
"email": "bobtony"
},
"twitter:123": {
"username": "twitter",
"email": "Andrew Lee"
},
"facebook:456": {
"username": "facebook",
"email": "James Tamplin"
}
}
}
And last but not least the retreiving of the data, this can be done in several ways but for this example i'm gonna use a simple example from the firebase guide: (source: Read and Write data) (old source: Retreiving Data)
//Get the current userID
var userId = firebase.auth().currentUser.uid;
//Get the user data
return firebase.database().ref('/users/' + userId).once('value').then(function(snapshot) {
//Do something with your user data located in snapshot
});
EDIT: Added example of return data
So when you are logged in as user twitter:123 you will get a reference to the location based on your user id and will get this data:
"twitter:123": {
"username": "twitter",
"email": "Andrew Lee"
}
Though I agree with Andre about setting the rules for good security - I would handle the data a bit differently. Instead of generating the string I use the child() method. It's a matter of personal preference.
Get the UID and define a data object:
let user = firebase.auth().currentUser
let uid = user.uid
let yourdata = { foo: 'something', bar: 'other'}
Save the data:
firebase.database().ref('users').child(uid).set(yourdata)
.then((data) => {
console.log('Saved Data', data)
})
.catch((error) => {
console.log('Storing Error', error)
})
Fetch the data:
firebase.database().ref('users').child(uid).once('value')
.then((data) => {
let fetchedData = data.val()
console.log('Fetched Data', fetchedData)
})
.catch((error) => {
console.log('Fetching Error', error)
})
Please notice that set() will override your data so you might want to use push() later on.

Categories