Using GetStream API, I am getting incorrect unseen and unread counts if notifications are grouped - notifications

I am creating notification on server side following way
String target = "oxyn"+notif.getTarget().getOid();
log.info("creating notification for target user {}",target);
NotificationFeed notifications= client.notificationFeed("notification",target);
notifications.addActivity(Activity.builder()
.actor(notif.getTarget().getName())
.verb("receive")
.object(notif.getOid()+"")
.foreignID(notif.getTarget().getName()+":"+notif.getOid())
.extraField("message", notif.getMessage())
.extraField("action", notif.getAction())
.extraField("sender", notif.getSender().getOid())
.extraField("oxyn", notif.getOxyn())
.build()).join();
and on client side, when new notification is sent I am calling
notification1 = client.feed('notification', ("oxyn"+me.oid));
notification1.get({mark_seen:false,mark_read:false})
.then(function(data) {
/* on success */
console.log("new : "+data.unseen);
})
.catch(function(reason) { /* on failure */
alert(reason);
});
problem is, my notifications get grouped and if there is more than one new notification (for example 3) properties unseen/unread count still say 1 instead of 3.
so the only workaround I found is to make sure each notification is unique within unique group so I make verb unique...
.verb("receive"++notif.getOid())
it seems to do the job, notifications do not get grouped, but I feel like this is a hack, so
my question is how do I get a correct number for unseen/unread if my notifications are grouped?

Unseen and Unread counts are based on the amount of groups that are unread/unseen, not on the individual activities. Notification feeds support the most common use-case out of the box: Facebook's notification feed.
What you want in this case is to aggregated activities by their id ({{ id }}). When you do that every group will always have 1 activity and every insert will increase the unseen/unread by one.
This is similar to what you are doing except that you don't have to hack the uniqueness on the verb. To do this you need to configure your notification feed group via Stream's Dashboard and change the aggregation format into {{ id }}.

Related

How to use numeric chat IDs to avoid expensive `get_entity(channel_name)` calls?

As per this comment, I'm trying to use numeric channel IDs in my telethon code, so that I don't end up spamming the Telegram API with expensive name lookup calls and getting throttled, but I'm having some difficulty.
e.g. assuming I've already instantiated and connected client:
messages = client.get_messages(numeric_channel_id)
...fails with this error:
ValueError: Could not find the input entity for PeerUser(user_id=[numeric_channel_id]) (PeerUser)
I think there's some cacheing going on, because if I do a get_entity call using the account name first, then the get_messages call works. i.e. something like this:
client.get_entity(channel_name_which_belongs_to_numeric_channel_id)
messages = client.get_messages(numeric_channel_id)
That works just fine, but now I'm doing the expensive get_entity(name) call which is what I'm trying to avoid (because it will result in FloodWaitError problems).
Is there any way I can use the numeric ID of a channel to avoid the expensive get_entity call, in this scenario?
I've also tried forcing the entity type to Channel, like this:
channel = Channel(id=numeric_channel_id, title=None, photo=None, date=None)
messages = client.get_messages(channel)
...but the results are the same, except that the error mentions PeerChannel rather than PeerUser
ID usage is not going to work unless you cached the target as you stated, that's the only way to use the integer id.
you must have met the entity from events or manual requests (say, username fetching).
you should be using client.get_input_entity('username')
it will try to search the local cache first for the saved id + hash that equals the passed username, if found it won't do ResolveUsername (heavy one) and use the local access_hash + id and return you an inputPeer. you pass that to any request you want.
you mustn't use id alone unless you're certain you have met its holder, in other words, id you use has to be something you found out from within the library and within the same session, not something you knew/found out externally.
There is no magical way to fetch something with id you claim you know, if you actually know it, the lib has to create (when the access_hash is present) an InputPeer
As the other answer states, fetching by username will always work but is expensive. However note that such a call will fill the cache so it can later be fetched again much more cheaply by ID.
If you really need a stable reference to some entity and cannot rely on the session cache, and want to avoid usernames, the documentation for Entities vs. Input Entities may be helpful.
What it boils down to is, you can do this:
print(await client.get_input_entity('username'))
...which will show something like:
InputPeerChannel(channel_id=1066197625, access_hash=-6302373944955169144)
...and then, the account that made the get_input_entity call will always be able to use the printed result, without the need for it to be in cache:
from telethon.tl.types import InputPeerChannel
USERNAME = InputPeerChannel(channel_id=1066197625, access_hash=-6302373944955169144)
# ...
await client.send_message(USERNAME, 'Hi') # works without cache

Firebase concurrency of reading and writing

I've got the problem when two people at the same time tries to add each other to friends. So, at first when one person presses add to friends he checks, whether other user's groups doesn't have my ID. If he has, I don't need to create new group - I will just add this group's ID to my list, else - I will create new group. Now, when two people at the same time press that button, they both get the result that group doesn't exist thus they both create a new group. How to solve these kind of problems?
Structure:
"userGroups" : {
"myId1" : {
"generatedGroupId1" : "myFriendID1"
}
}
Update: I've managed to do it: basically in doTransaction I create group if it doesn't exist and then on onComplete I work with already created group. If two people start creating new group, one end up creating it, second one - reading it.
// function
ref.runTransaction(object : Transaction.Handler {
override fun doTransaction(currentData: MutableData): Transaction.Result {
// create group here if data is null
return Transaction.success(currentData)
}
override fun onComplete(
error: DatabaseError?,
committed: Boolean,
currentData: DataSnapshot?
) {
Log.d(TAG, "postTransaction:onComplete:" + error)
// continue doing stuff, group already exists at this point
}
})
}
It's hard to give specific without seeing your code, but the most likely options are (in order of my personal preference):
Base the group ID on the two users in it. If you do this correct, the two users will end up with the same group ID, and it doesn't matter who is first. Creating the groups then becomes a so-called idempotent operation, which means that if you run the same operation multiple times, it will have the same result. For an example of such group IDs, see Best way to manage Chat channels in Firebase
Use a transaction to ensure only one write makes it through. This would mean that the second user ends up reading the group created by the first user, and can then cancel their data creation.
Use a Cloud Function, which can perform more (non-atomic) read operations to check whether the group of users already exists, and reject the request from the second user.

How to attach multiple parameters to an API request?

I built my own simple REST API with Express and now I'm consuming it from my client (Vue.js)
So in my page I access all the data from this endpoint: GET /api/books, and it works fine
Now I also have a "sort by" button where I want to get the data by the latest entries. I don't know if that's a good way or if I have to handle this in the backend but what I do is calling the same endpoint which is GET /api/books and sorting the data to get them the right way
For ex:
sortByLatest() {
axios
.get("/api/books")
.then(res => {
const books = res.data;
const sortedBooks = books.sort((a, b) => b.createdAt > a.createdAt ? 1 : -1
);
this.books = sortedBooks;
})
// catch block
}
I do that for everything. If I need a limited number of results or a specific property from the data I have to write some logic in the axios .then block to sort or filter what I want. Is this bad practice?
But that's not my actual problem
My problem is, in addition of sorting by the latest entries, I also want to filter the results by a specific property. The problem is when I click the A button it's gonna filter the books by a specific property, and when I click the B button it's gonna sort them buy the latest entries, but not both at the same time!
And what if I want additionnal things like limit the number of results to 10, filter by other properties etc... I want to be able to create requests that ask all those things at once. How can I do that? Do I have to build that in the backend?
I saw some websites using url parameters to filter stuff, like /genre=horror&sort=latest, is that the key of doing it?
Thank you for your time

How can I create a device group in Cumulocity with REST

I am writing a service for forwarding our sensor data to Cumulocity platform. I designed the structure so that all the data is first sent to our main tenant and then device data for each customer is forwarded to corresponding tenants with Data Broker.
I can group devices manually and forward by group but I don't want to deal with it every time a new device is added.Sensor data contains customer name. Probably I can add customer name to device properties (like device_type) and use that as a filter but I want to avoid that if possible. So I thought, when a sensor data hits my endpoint, I do something like this:
Look if the device exists in the database.If it exists just publish measurement data.
If not look at the Group database(Just a key-value store containing customer names and managed Object Ids of the corresponding groups from Cumulocity) to see if there is entry for the customer.
If not add an entry and create a group with customer name.Then add the device to the group.
If it exists, just add the device to the group.
I tried adding devices to groups with REST and it works. The problem is I cannot create a device group with REST.
I looked at the Cumulocity API example requests and tried to tweak them a little.
I tried sending POST request to {{url}}/inventory/managedObjects as:
{
"name": "TestDeviceGroup",
"c8y_IsDeviceGroup": {}
}
It returns 201 created but I cannot see the group. When I try to get collection of groups I see it there as a managed object with a new Id.
I tried to add a new device to this object as a child asset.
{{url}}/inventory/managedObjects/{{GroupId}}/childAssets
{
"managedObject": "id:{{deviceId}}"
}
It returns 201 created but device GROUP is not updated.
If I recreate this scenario with a group created with UI and its Id everything works fine and device is added to the group.
As I understand what I create is not a legit device group and that is the main problem. So my question is How can I create device group with REST?
To create the group you were already on the right track you are just missing the correct type. Create your group like this:
POST /inventory/managedObjects
{
"name": "TestDeviceGroup",
"type": "c8y_DeviceGroup",
"c8y_IsDeviceGroup": {}
}
To assign your device to a particular group you can EITHER assign an existing device to an existing group like this (replace the placeholders in <> with your IDs):
POST /inventory/managedObjects/<groupId>/childAssets
{
"managedObject": {"id":"<deviceId>"}
}
Or you can directly create a new device into an existing group like this:
POST /inventory/managedObjects/<groupId>/childAssets
Content-Type: application/vnd.com.nsn.cumulocity.managedobject+json
{
"name": "my device",
"c8y_IsDevice": {}
}

RESTful API - How do I return different results for the same resource?

Question
How do I return different results for the same resource?
Details
I have been searching for some time now about the proper way to build a RESTful API. Tons of great information out there. Now I am actually trying to apply this to my website and have run into a few snags. I found a few suggestions that said to base the resources on your database as a starting point, considering your database should be structured decently. Here is my scenario:
My Site:
Here is a little information about my website and the purpose of the API
We are creating a site that allows people to play games. The API is supposed to allow other developers to build their own games and use our backend to collect user information and store it.
Scenario 1:
We have a players database that stores all player data. A developer needs to select this data based on either a user_id (person who owns the player data) or a game_id (the game that collected the data).
Resource
http://site.com/api/players
Issue:
If the developer calls my resource using GET they will receive a list of players. Since there are multiple developers using this system they must specify some ID by which to select all the players. This is where I find a problem. I want the developer to be able to specify two kinds of ID's. They can select all players by user_id or by game_id.
How do you handle this?
Do I need two separate resources?
Lets say you have a controller name 'Players', then you'll have 2 methods:
function user_get(){
//get id from request and do something
}
function game_get(){
//get id from request and do something
}
now the url will look like: http://site.com/api/players/user/333, http://site.com/api/players/game/333
player is the controller.
user/game are the action
If you use phil sturgeon's framework, you'll do that but the url will look like:
http://site.com/api/players/user/id/333, http://site.com/api/players/game/id/333
and then you get the id using : $this->get('id');
You can limit the results by specifying querystring parameters, i.e:
http://site.com/api/players?id=123
http://site.com/api/players?name=Paolo
use phil's REST Server library: https://github.com/philsturgeon/codeigniter-restserver
I use this library in a product environment using oauth, and api key generation. You would create a api controller, and define methods for each of the requests you want. In my case i created an entirely seperate codeigniter instance and just wrote my models as i needed them.
You can also use this REST library to insert data, its all in his documentation..
Here is a video Phil threw together on the basics back in 2011..
http://philsturgeon.co.uk/blog/2011/03/video-set-up-a-rest-api-with-codeigniter
It should go noted, that RESTful URLs mean using plural/singular wording e.g; player = singular, players = all or more than one, games|game etc..
this will allow you to do things like this in your controller
//users method_get is the http req type.. you could use post, or put as well.
public function players_get(){
//query db for players, pass back data
}
Your API Request URL would be something like:
http://api.example.com/players/format/[csv|json|xml|html|php]
this would return a json object of all the users based on your query in your model.
OR
public function player_get($id = false, $game = false){
//if $game_id isset, search by game_id
//query db for a specific player, pass back data
}
Your API Request URL would be something like:
http://api.example.com/player/game/1/format/[csv|json|xml|html|php]
OR
public function playerGames_get($id){
//query db for a specific players games based on $userid
}
Your API Request URL would be something like:
http://api.example.com/playerGames/1/format/[csv|json|xml|html|php]