Laravel Lighthouse, how can i get all array data when i do subscription? - subscription

I do subscription with laravel + Lighthouse + Laravel WebSockets + vue-apollo tech.
When i subscription, i wanna get all array data, but i only got changed data.
My schema.graphql is below.
type Mutation {
updateTest(id: ID!, name: String, result: Int): Test #update
#broadcast(subscription: "testSub")
}
type Subscription {
testSub(id: ID): Test
}
type Test {
id: ID!
name: String
result: Int
}
This is my vue-apollo code
const subQuery = gql`subscription testSub($id: ID) {
testSub(id:$id) {
id
name
}
}`
const observer = this.$apollo.subscribe({
query: subQuery,
variables () {
return {
id: 14,
}
},
})
observer.subscribe({
next (data) {
console.log('subscribe')
console.log(data)
},
error (error) {
console.log('err')
console.error(error)
},
})
When i do mutation like below.
mutation {
updateTest(id:14, name: "hahaha", result:1) {
id
name
}
}
vue-apollo get subscription like pic.
I recognized return is only changed value instead of all data.
So i change subscription schema like below.
type Subscription {
testSub: [Test] #all
}
I also changed vue-apollo code.
const subQuery = gql`subscription testSub { #delete argument
testSub { #delete argument
id
name
}
}`
const observer = this.$apollo.subscribe({
query: subQuery,
variables () {
return {
id: 14,
}
},
})
observer.subscribe({
next (data) {
console.log('subscribe')
console.log(data)
},
error (error) {
console.log('err')
console.error(error)
},
})
When i do mutation after npm run dev and websocket start, i got this error.
But i already made testSub.
php artisan lighthouse:subscription testSub
This is my testSub file.
<?php
namespace App\GraphQL\Subscriptions;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Subscriptions\Subscriber;
use Nuwave\Lighthouse\Schema\Types\GraphQLSubscription;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
use App\Test as Test2;
use App\Events\test;
class TestSub extends GraphQLSubscription
{
/**
* Check if subscriber is allowed to listen to the subscription.
*
* #param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
* #param \Illuminate\Http\Request $request
* #return bool
*/
public function authorize(Subscriber $subscriber, Request $request): bool
{
// TODO implement authorize
return true;
}
/**
* Filter which subscribers should receive the subscription.
*
* #param \Nuwave\Lighthouse\Subscriptions\Subscriber $subscriber
* #param mixed $root
* #return bool
*/
public function filter(Subscriber $subscriber, $root): bool
{
return true;
// TODO implement filter
}
public function encodeTopic(Subscriber $subscriber, string $fieldName): string
{
// Optionally create a unique topic name based on the
// `author` argument.
//$args = $subscriber->args;
//return Str::snake($fieldName).':'.$args['author'];
//return Str::snake($fieldName).':1';
return 'testSub';
}
/**
* Decode topic name.
*
* #param string $fieldName
* #param \App\Post $root
* #return string
*/
public function decodeTopic(string $fieldName, $root): string
{
// Decode the topic name if the `encodeTopic` has been overwritten.
$author_id = $root->author_id;
//return Str::snake($fieldName).':'.$author_id;
return 'testSub';
}
public function resolve($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Test2
{
event(new test());
return $root;
}
}
How can i get all array data instead of changed data?

In your vue-apollo code you have this:
gql`subscription testSub($id: ID) {
testSub(id:$id) {
id
name
}
}`
So, think that it is like a query. Everytime the subscription is fired, you are querying the id and name. If you want to also query the result, just add it:
gql`subscription testSub($id: ID) {
testSub(id:$id) {
id
name
result # <--
}
}`
There is no way to tell to apollo that has to fetch "all" fields of the Test type. You have to explicit those fields.

Related

How to send notifications to Laravel Users with Onesignal notification channel?

I'm trying to use laravel-notification-channels/onesignal with Laravel 9, Inertia React project.
For first I setup the client in this way:
useEffect(() => {
OneSignal.init({
appId: "PRIVATE-KEY"
});
}, []);
Testing from Onesignal panel the client is listening.
For the back-end I have created a Notification:
<?php
namespace App\Notifications;
use NotificationChannels\OneSignal\OneSignalChannel;
use NotificationChannels\OneSignal\OneSignalMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\BroadcastMessage;
use App\Models\Order;
class OrderPlacedNotification extends Notification
{
use Queueable;
public $order;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['database', 'broadcast', OneSignalChannel::class];
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toArray($notifiable)
{
return [
'order' => $this->order,
];
}
public function toBroadcast($notifiable)
{
return new BroadcastMessage([
'order' => $this->order
]);
}
public function toOneSignal($notifiable)
{
return OneSignalMessage::create()
->setSubject("Nuovo ordine!")
->setBody("Vedi l'ordine.")
->setUrl('http://onesignal.com');
}
}
and I send the notification via controller to all users.
All config setted but I can't listen to the user.
I found the solution. In my client I subscibe user to the specific interest and in backend I send notifictions at the users with that specific interest:
Frontend
useEffect(() => {
if(auth.user) {
window.Echo.private(`App.Models.User.${auth.user.id}`).notification(notification => {
console.log(notification);
setNotifications(() => [...notifications, notification]);
})
OneSignal.init(
{
appId: "KEY",
},
//Automatically subscribe to the new_app_version tag
OneSignal.sendTag("orders", "orders", tagsSent => {
// Callback called when tag has finished sending
console.log('TAG SENT', tagsSent);
})
);
}
}, [notifications]);
User Model:
public function routeNotificationForOneSignal()
{
return ['tags' => ['key' => 'orders', 'relation' => '=', 'value' => 'orders']];
}

Laravel 5.8 and React Native using pusher: error listening to private channel: No callbacks on conversations34 for pusher:subscription_succeeded

The problem is when the app developer tries to listen to the event via pusher private channel. Here's the laravel code.
routes/api.php
Route::middleware('auth:api')->post('/broadcast/auth', 'Api\Auth\BroadcastAuthController#auth');
routs/channels.php:
// notifications channel
Broadcast::channel('users.{id}', function ($user, $id) {
return (int)$user->id === (int)$id;
});
// conversations channel
Broadcast::channel('conversations.{conversation}', function ($user, \App\Models\Conversation
$conversation) {
return $conversation->users->contains($user);
});
MessageSent event
class MessageSent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($message)
{
$this->message = $message;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('conversations.' . $this->message->conversation_id);
}
/**
* Load user relation.
*
* #return array
*/
public function broadcastWith()
{
return [
'message' => $this->message->load('user')
];
}
}
React receiving the message
var Echotest= new Echo({
broadcaster: 'pusher',
key: 'xxxxxxxxxxx',
forceTLS: true,
cluster:"eu",
authEndpoint: 'https://example.com/api/broadcast/auth' ,
auth: {
headers: {
Authorization: "Bearer " + token ,
}
}
});
console.log(Echotest);
Echotest.channel("conversations" + this.state.conversation_id)
.listen("App\Events\MessageSent", e => {
alert(e)
});
But the result is:
Pusher : Event sent : {"event":"pusher:subscribe","data":{"channel":"conversations34"}}
Pusher : Event recd :
{"event":"pusher_internal:subscription_succeeded","channel":"conversations34","data":{}}
Pusher : No callbacks on conversations34 for pusher:subscription_succeeded
Is there anything else need to be done in Laravel to make it work in react? or the issue in react and need to be fixed? Please advise and thanks in advance.
Just Change the "Echotest.channel" to "Echotest.private"

ng:areq fn is not a function got objectService angularjs es6

I have 1 service which extends other. When I try to import it in controller got this error ng:areq fn is not a function got UserRepository. I think my problem is in right way injecting this service. Here is my Service:
import { USER_REPO_CONFIG } from '../repository.config';
import { AbstractRepository } from '../abstract.repository';
import { UserModel } from './user.model';
import 'rxjs/add/operator/map';
class UserRepository extends AbstractRepository {
constructor($http) {
'NgInject'
super($http, USER_REPO_CONFIG, UserModel);
}
/**
* Gets the user by ID
*
* #param {number} userId - ID of a user to find
* #returns {Observable<UserModel>} - User model instance
*/
getUser(userId) {
return this.getItem(userId);
}
/**
* Creates the user
*
* #param {UserModel} user - User model instance
* #returns {Observable<UserModel>} - User model instance observable
*/
createUser(user) {
return this.createItem(user);
}
/**
* Updates the user
*
* #param {number} userId - ID of a user to update
* #param {UserModel} user - User model instance
* #returns {Observable<UserModel>} - User model instance observable
*/
updateUser(userId, user) {
return this.updateItem(userId, user);
}
}
export { UserRepository };
here is my Module:
import { HelloComponent } from './hello/hello.component';
import { UserRepository } from '../core/repository/user/user.repository';
const greetingModule = angular
.module('app.greeting',[])
.component('helloComponent', new HelloComponent)
.service('UserRepository', new UserRepository)
.name;
export { greetingModule };
and my Controller:
import { UserRepository } from '../../core/repository/user/user.repository';
class HelloController {
constructor(UserRepository) {
this.hello = 'Hello World';
this.service = UserRepository;
}
getAll() {
console.log('user Repository', this.service)
}
}
HelloController.$inject = ['UserRepository'];
export { HelloController };
.service('UserRepository', new UserRepository)
.service() expects a function, you pass an object. You can pass the class directly:
.service('UserRepository', UserRepository)
Passing the class effectively passes the constructor function.

How to save Mobx state in sessionStorage

Trying to essentially accomplish this https://github.com/elgerlambert/redux-localstorage which is for Redux but do it for Mobx. And preferably would like to use sessionStorage. Is there an easy way to accomplish this with minimal boilerplate?
The easiest way to approach this would be to have a mobx "autorun" triggered whenever any observable property changes. To do that, you could follow my answer to this question.
I'll put some sample code here that should help you get started:
function autoSave(store, save) {
let firstRun = true;
mobx.autorun(() => {
// This code will run every time any observable property
// on the store is updated.
const json = JSON.stringify(mobx.toJS(store));
if (!firstRun) {
save(json);
}
firstRun = false;
});
}
class MyStore {
#mobx.observable prop1 = 999;
#mobx.observable prop2 = [100, 200];
constructor() {
this.load();
autoSave(this, this.save.bind(this));
}
load() {
if (/* there is data in sessionStorage */) {
const data = /* somehow get the data from sessionStorage or anywhere else */;
mobx.extendObservable(this, data);
}
}
save(json) {
// Now you can do whatever you want with `json`.
// e.g. save it to session storage.
alert(json);
}
}
Turns out you can do this in just a few lines of code:
const store = observable({
players: [
"Player 1",
"Player 2",
],
// ...
})
reaction(() => JSON.stringify(store), json => {
localStorage.setItem('store',json);
}, {
delay: 500,
});
let json = localStorage.getItem('store');
if(json) {
Object.assign(store, JSON.parse(json));
}
Boom. No state lost when I refresh the page. Saves every 500ms if there was a change.
Posting the example from here: https://mobx.js.org/best/store.html
This shows a cleaner method of detecting value changes, though not necessarily local storage.
import {observable, autorun} from 'mobx';
import uuid from 'node-uuid';
export class TodoStore {
authorStore;
transportLayer;
#observable todos = [];
#observable isLoading = true;
constructor(transportLayer, authorStore) {
this.authorStore = authorStore; // Store that can resolve authors for us
this.transportLayer = transportLayer; // Thing that can make server requests for us
this.transportLayer.onReceiveTodoUpdate(updatedTodo => this.updateTodoFromServer(updatedTodo));
this.loadTodos();
}
/**
* Fetches all todo's from the server
*/
loadTodos() {
this.isLoading = true;
this.transportLayer.fetchTodos().then(fetchedTodos => {
fetchedTodos.forEach(json => this.updateTodoFromServer(json));
this.isLoading = false;
});
}
/**
* Update a todo with information from the server. Guarantees a todo
* only exists once. Might either construct a new todo, update an existing one,
* or remove an todo if it has been deleted on the server.
*/
updateTodoFromServer(json) {
var todo = this.todos.find(todo => todo.id === json.id);
if (!todo) {
todo = new Todo(this, json.id);
this.todos.push(todo);
}
if (json.isDeleted) {
this.removeTodo(todo);
} else {
todo.updateFromJson(json);
}
}
/**
* Creates a fresh todo on the client and server
*/
createTodo() {
var todo = new Todo(this);
this.todos.push(todo);
return todo;
}
/**
* A todo was somehow deleted, clean it from the client memory
*/
removeTodo(todo) {
this.todos.splice(this.todos.indexOf(todo), 1);
todo.dispose();
}
}
export class Todo {
/**
* unique id of this todo, immutable.
*/
id = null;
#observable completed = false;
#observable task = "";
/**
* reference to an Author object (from the authorStore)
*/
#observable author = null;
store = null;
/**
* Indicates whether changes in this object
* should be submitted to the server
*/
autoSave = true;
/**
* Disposer for the side effect that automatically
* stores this Todo, see #dispose.
*/
saveHandler = null;
constructor(store, id=uuid.v4()) {
this.store = store;
this.id = id;
this.saveHandler = reaction(
// observe everything that is used in the JSON:
() => this.asJson,
// if autoSave is on, send json to server
(json) => {
if (this.autoSave) {
this.store.transportLayer.saveTodo(json);
}
}
);
}
/**
* Remove this todo from the client and server
*/
delete() {
this.store.transportLayer.deleteTodo(this.id);
this.store.removeTodo(this);
}
#computed get asJson() {
return {
id: this.id,
completed: this.completed,
task: this.task,
authorId: this.author ? this.author.id : null
};
}
/**
* Update this todo with information from the server
*/
updateFromJson(json) {
// make sure our changes aren't send back to the server
this.autoSave = false;
this.completed = json.completed;
this.task = json.task;
this.author = this.store.authorStore.resolveAuthor(json.authorId);
this.autoSave = true;
}
dispose() {
// clean up the observer
this.saveHandler();
}
}
Here, you can use my code, although it only supports localStorage you should be able to modify it quite easily.
https://github.com/nightwolfz/mobx-storage

Error: field type must be Input Type - cannot pass qlType into mutation

I want to update a person with the UpdatePerson mutation. I don't want to specify each and every property in the inputFields - but rather want to pass the complete person object.
When I do that I get Error: UpdatePersonInput.person field type must be Input Type but got: Person.
Is there no way to pass complete objects rather than all of their properties to a mutation?
If there isn't, could you add one - because the amount of repetition of fields across a larger app with bigger objects can become very frustrating.
Same might be an issue on getFatQuery and static fragments. Repeating all the properties over and over again would be a nightmare.
Server:
/**
* Create the GraphQL Mutation.
*/
export default mutationWithClientMutationId({
// Mutation name.
name: 'UpdatePerson',
// Fields supplied by the client.
inputFields: {
person: {type: qlPerson} // <========================================
},
// Mutated fields returned from the server.
outputFields: {
person: {
type: qlPerson,
// Parameters are payload from mutateAndGetPayload followed by outputFields.
resolve: (dbPerson, id, email) => {
return dbPerson;
}
}
},
// Take the input fields, process the mutation and return the output fields.
mutateAndGetPayload: ({qlPerson}, {rootValue}) => {
// TODO: Process Authentication {"session":{"userId":1}}
console.log(JSON.stringify(rootValue));
// Convert the client id back to a database id.
var localPersonId = fromGlobalId(qlPerson.id).id;
// Find the person with the given id in the database.
return db.person.findOne({where: {id: localPersonId}}).then((dbPerson)=> {
// Mutate the person.
dbPerson.email = qlPerson.email;
// Save it back to the database.
return dbPerson.save().then(()=> {
// Return the mutated person as an output field.
return dbPerson;
});
});
}
});
Client:
/**
* Create the GraphQL Mutation.
*/
class UpdatePersonMutation extends Relay.Mutation {
getMutation() {
return Relay.QL`mutation {updatePerson}`;
}
getVariables() {
return {person: this.props.person}; // <========================================
}
getFatQuery() {
return Relay.QL`
fragment on UpdatePersonPayload {
person {
email, // ??????????????????????????
}
}
`;
}
getConfigs() {
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {
person: this.props.person.id
}
}];
}
static fragments = {
person: () => Relay.QL`
fragment on Person {
id,
email // ???????????????????????????
}
`
};
getOptimisticResponse() {
return {
person: this.props.person
};
}
}
/**
* Exports.
*/
export default UpdatePersonMutation;
It's error because your qlPerson type was defined by using the GraphQLObjectType class, which is not an Input type. You must define it using the GraphQLInputObjectType instead. Basically, both of them take an object as an argument which requires same properties. So, you just need to use GraphQLInputObjectType instead of GraphQLObjectType as following:
export default new GraphQLInputObjectType({
name: 'qlPerson',
description: 'Dietary preferences',
fields: () => ({
firstName: {type: GraphQLString},
...
})
});

Resources