Feathers.js -> Change the layout of the after create hook - feathersjs

I've set up nuxt.js, converting my feathers project. So far it is functioning. Added something through postman appears in my browser on the webpage. There is on difference, namely when I post something the entire dataarray is pushed which doesn't match the initial array.
Sample
{ "message_txt": "Hello todays2" }
{ "id": 17, "message_txt": "Hello YESd", "updated_at": "2017-03-25T11:15:44.000Z", "created_at": "2017-03-25T11:15:44.000Z" }
So the first line is how the data is configured to be returned. This is set in my hooks file:
'use strict';
const globalHooks = require('../../../hooks/index');
const hooks = require('feathers-hooks');
exports.before = {
all: [],
find: [
getRelatedInfo()
],
get: [
getRelatedInfo()
],
create: [],
update: [],
patch: [],
remove: []
};
exports.after = {
all: [],
find: [],
get: [],
create: [
getRelatedInfo()
],
update: [],
patch: [],
remove: []
};
function getRelatedInfo() {
return function (hook) {
hook.params.sequelize = {
attributes: [
'message_txt'
],
order: [
['created_at', 'DESC']
]
};
return Promise.resolve(hook);
};
}
Currently, my client loads the data with this part:
<template>
<div>
idnex.vue
<ul>
<li v-for="message in messages">
{{ message }}
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios';
import feathers from 'feathers/client';
import socketio from 'feathers-socketio/client';
import io from 'socket.io-client';
export default {
data: function() {
return {
messages: []
}
},
mounted: function() {
axios.get('http://localhost:3001/messages')
.then((response) => {
this.messages = response.data.data
});
const app = feathers().configure(socketio(io('http://localhost:3001')));
app.service('messages').on('created', (message) => {
this.messages.push(message);
})
}
}
</script>
Question
What do I need to do make sure the "this.messages.push(message)" is in the same structure, so only "message_txt". I tried adding the function in the hooks to the "after create" but it doesn't work.
Solving
Okay, so changing the component file of the client gives access to the service, model, and hooks and what else of the service as configured in the client. The 'service.find()' is just there for I copied it from a more complex service where more columns are in the find hook and I only want specific ones.
<template>
<div>
idnex.vue
todo: eagle.js slideshow
todo: first info
<ul>
<li v-for="message in listMessages">
{{ message }}
</li>
</ul>
</div>
</template>
<script>
import feathers from 'feathers/client';
import socketio from 'feathers-socketio/client';
import hooks from 'feathers-hooks';
import io from 'socket.io-client';
import * as process from "../nuxt.config";
const vttSocket = io(process.env.backendUrl);
const vttFeathers = feathers()
.configure(socketio(vttSocket))
.configure(hooks());
const serviceMessage = vttFeathers.service('messages');
export default {
layout: 'default',
data: function() {
return {
listMessages: []
}
},
mounted: function() {
//TODO change the message layout to the correct layout
serviceMessage.find({
query: {
$sort: {
created_at: 1
},
$select: [
'message_txt'
]
}
}).then(page => {
this.listMessages = page.data;
});
serviceMessage.on('created', (serviceMessage) => {
this.listMessages.push(serviceMessage);
});
}
}
</script>
With this code, the page still loads but the 'this.listMessages' still also give stuff like the created_at. Also, when I didn't have nuxt and a client/server situation (client and server were one) I didn't need the 'on created' for the list displayed to be updated real time. I still need to have the line for realtime update.
Important note, I just noticed that in postman you also see the entire record information and not the same information as with the GET
So nearly there. I figured out what to do in my hooks file
exports.after = {
all: [],
find: [],
get: [],
create: [
hooks.remove('createdAt'),
hooks.remove('updatedAt')
],
update: [],
patch: [],
remove: []
};
In this file I have the above section. If I change the column in the remove to a 'custom' one (which is in the model) it will work. These two don't work

this.messages.push(message.message_txt); ?...
Ahh I think I see what you are really asking now. Only params.query will be passed between the client and the server for security reasons. If you would like to set params.sequelize based on query parameters you have to sanitize and map it in a hook:
app.service('myservice').before(function(hook) {
const query = hook.params.query;
hook.params.sequelize = {};
if(query.someSequelizeParameter) {
hook.params.sequelize.myParameter = sanitize(query.someSequelizeParameter);
delete query.someSequelizeParameter;
}
});
The other options is to just use Feathers hooks instead. Also, not part of your question, but if you are using sockets anyway, you really don't need a need Axios and the REST stuff updated via methods, you can just tie the service to the socket and use something like RxJS to stream all updates in real time. What you are doing is fine and will work, but with a lot more code to maintain.

Related

Accessing Vuex Store Before Page Load NuxtJS

Context: I am trying to get Google Maps place data via the place_id on the beforeEnter() route guard. Essentially, I want the data to load when someone enters the url exactly www.example.com/place/{place_id}. Currently, everything works directly when I use my autocomplete input and then enter the route but it does not work when I directly access the url from a fresh tab. I've been able to solve this using the beforeEnter() route guard in traditional Vue, but cannot solve for this using Nuxt. Please help!
Question: How can I access the Vuex Store before a page loads in Nuxt?
Error: Any solution I try (see below) I either end up with a blank page or the page will not load (I think it is stuck in a loop and cannot resolve the Promise).
Attempted Solutions:
Using Middleware like below:
middleware({ store, params }) {
return store.dispatch('myModule/fetchLocation', params.id)
}
Using asyncData like below:
data(){
return{
filteredLocation: {}
}
}
// snip
async asyncData({ store, params }) {
const { data } = await store.dispatch('myModule/fetchLocation', params.id)
return filteredLocation = data
}
I tried looking into fetch, but apparently you no longer have access to context
Example Code:
In one of my store modules:
/* global google */
import Vue from 'vue'
import * as VueGoogleMaps from '~/node_modules/vue2-google-maps/src/main'
Vue.use(VueGoogleMaps, {
load: {
key: process.env.VUE_APP_GMAP_KEY,
libraries: 'geometry,drawing,places'
}
})
export const state = () => ({
selectedLocation: {}
})
export const actions = {
fetchLocation({ commit }, params) {
return new Promise((resolve) => {
Vue.$gmapApiPromiseLazy().then(() => {
const request = {
placeId: params,
fields: [
'name',
'rating',
'formatted_phone_number',
'geometry',
'place_id',
'website',
'review',
'user_ratings_total',
'photo',
'vicinity',
'price_level'
]
}
const service = new google.maps.places.PlacesService(
document.createElement('div')
)
service.getDetails(request, function(place, status) {
if (status === 'OK') {
commit('SET_PLACE', place)
resolve()
}
})
})
})
}
}
export const mutations = {
SET_PLACE: (state, selection) => {
state.selectedInstructor = selection
}
}
EDIT: I already have it in a plugin named google-maps.js and in my nuxt.config.js file I have:
plugins: [
{ src: '~/plugins/google-maps.js' }
]
//
//
build: {
transpile: [/^vue2-google-maps.js($|\/)/],
extend(config, ctx) {}
}
Using Middleware is how we can access Vuex before page loads. try putting the configuration part in a custom Nuxt plugin.
Create a file in Plugins folder (you can name it global.js).
Put this
import Vue from 'vue'
import * as VueGoogleMaps from '~/node_modules/vue2-google-maps/src/main'
Vue.use(VueGoogleMaps, {
load: {
key: process.env.VUE_APP_GMAP_KEY,
libraries: 'geometry,drawing,places'
}
})
in global.js.
Then add the plugin in nuxt.config.js like this.
plugins: [
'~/plugins/global.js'
]
Also, make sure you're using underscore before 'page_id' name in your folder structure.

Data from API is displaying in the console but not in the DOM, why?

I'm learning React and a little about API's. I'm using the Destiny 2 API as a starting API to try to wrap my head around how they work.
Here is my Api.js file:
import React, { Component } from 'react';
import './style.css';
import axios from 'axios';
class Api extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentDidMount() {
let config = {
headers: {
'X-API-KEY': 'key-here',
},
};
axios
.get('https://www.bungie.net/Platform/Destiny2/4/Profile/4611686018484544046/?components=100', config)
.then((response) => {
console.log(response);
this.setState({
data: response.data,
});
});
}
render() {
const { item } = this.state;
return (
<div>
{Array.isArray(item) &&
item.map((object) => <p key={object.data}>{object.data.Response.profile.data.userInfo.displayName}</p>)}
</div>
);
}
}
export default Api;
The data from the API is returned as an object that contains a nested array. I can get the data to display in the console no problem.
This is the layout of the response object output to the console:
I'm trying to grab the value of "displayName" and output it into the DOM, what am I doing wrong?
I have tried returning the data as JSON by doing:
response => {return(data.json())} and iterating through the json object using {Object.keys(this.state.data).map((key) => but I have still managed to only get data in the console and not in the DOM.
Is there anything that seems to be missing? I've been stuck with this problem for several days now!
EDIT: This is the whole response from the API call
{
"Response": {
"profile": {
"data": {
"userInfo": {
"membershipType": 4,
"membershipId": "4611686018484544046",
"displayName": "Snizzy"
},
"dateLastPlayed": "2019-04-05T14:28:30Z",
"versionsOwned": 31,
"characterIds": [
"2305843009409505097",
"2305843009411764917",
"2305843009425764024"
]
},
"privacy": 1
}
},
"ErrorCode": 1,
"ThrottleSeconds": 0,
"ErrorStatus": "Success",
"Message": "Ok",
"MessageData": {}
}
In the render function, where you destructure you state, you have the wrong property.
const { item } = this.state; should be const { data } = this.state;
More about destructuring here.
Also, you need to make changes here:
EDIT: Actually, your data isn't even an array. You don't have to iterate through it.
<div>
<p>{data.Response.profile.data.userInfo.displayName}</p>}
</div>
Let's do a check to make sure that we got back the api before running. You might be rendering before the api call is finished. Try using an inline statement.
{ item ? {Array.isArray(item) && item.map(object => (
<p key={object.data}>{object.data.Response.profile.data.userInfo.displayName}</p>
))}
:
<div>Loading...</div>

Calling a local json file and parsing data in ReactJS

I have the following json file. Right now, i have kept this in my ReactJS project itself locally. Later on planning to move in a server location.
abtestconfig.json
[
{
"abtestname": "expAButton",
"traffic": 1,
"slices":
[
"orange",
"blue"
]
},
{
"abtestname": "expTextArea",
"traffic": 0.5,
"slices":
[
"lightgrey",
"yellow"
]
}
]
I want to read and get data and parse it and apply in a function. I got some reference sample code and trying to use fetch api to react json file with the following code.
After reading this json file, i will have to pass the data in abtest function, as you can see now it's sending with hard coded value abtest('expAButton', 0.75).slices('orange', 'blue').run(function ()
I have the following doubts and wanted to get your guidance / clarification.
1. Is it correct way to read json file using fetch api? Is there any other best approach?
2. When I use fetch api like mentioned below, console log displays GET http://localhost:8080/abtesting/abtestconfig.json 404 (Not Found)
app.jsx file:
import './abtesting/abtestconfig.json';
class App extends React.Component {
constructor() {
super();
this.onClick = this.handleClick.bind(this);
this.onClickNewUser = this.handleNewUser.bind(this);
this.state = {
bgColor: '',
data: []
}
};
handleNewUser (event) {
abtest.clear();
window.location.reload();
}
render() {
return (
<Helmet
/>
<p><b>A/B Testing Experience</b></p>
<div className="DottedBox">
<p><button id = "newUserButton" onClick = {this.onClickNewUser} style={{backgroundColor:"red"}}>Welcome and click here</button></p>
</div>
);
}
handleClick () {
abtest('expAButton', 0.75).slices('orange', 'blue').run(function () {
expAButton.style.backgroundColor = this.slice.name;
});
}
setStyle (stylecolor) {
this.setState({
bgColor: stylecolor
})
}
componentDidMount () {
this.handleClick();
fetch('./abtesting/abtestconfig.json').then(response => {
console.log(response);
return response.json();
}).then(data => {
// Work with JSON data here
console.log(data);
}).catch(err => {
// Do something for an error here
console.log("Error Reading data " + err);
});
}
}
export default hot(module)(App);

Why is hook.data not persisted to the database using PATCH?

Well, I'm new to feathersjs and I'm right now trying to port a small express experiment to it. The internal stuff works well so far, I can create a database entry for a scaffolded service and it's persisted. Nice.
Now I tried to use postman to send an unauthenticated patch to the server, and the changes are not persisted. That must be something really stupid, but I'm not seeing it in the moment.
This is an extract from affected the hooks:
const updateLocation = function () {
return function (hook) {
if (hook.params.query.latitude !== undefined && hook.params.query.longitude !== undefined) {
return new Promise((resolve, reject) => {
lookup({
latitude: parseFloat(hook.params.query.latitude),
longitude: parseFloat(hook.params.query.longitude)
}, (info) => {
hook.data = Object.assign(hook.data, info)
resolve(hook)
})
})
} else {
return hook
}
}
}
To remove everything which might keep tricking me, I placed it directly in the patch before hook:
module.exports = {
before: {
all: [],
find: [],
get: [],
create: [
disallow('external')
],
update: [
disallow('external')
],
patch: [
updateLocation()
],
remove: []
},
after: {
all: [],
find: [],
get: [],
create: [],
update: [],
patch: [
hook => console.log(hook.data)
],
remove: []
}
If I call PATCH with latitude and longitude, I'd expect latitude and longitude (and some additional values computed by the lookup method) to be replaced in the database entry. The after hook prints the calculated values to the console, but they never find their way to the database.
So isn't PATCH meant to do what I expect? Or didn't I get some important conceptional idea?

Using Angular Observers to fetch data over a network

I'm working on an Angular app that contains a list of (financial) Trades that a user can add to. This has been going well, and I'm trying to switch over from a static list provided by a service to trying to fetch the data from a local Node.js server. I'm using an observer to asynchronously fetch the list of trades.
I've been following along with Angular's HTTP tutorial and the associated plunker. However, even though I can see the data coming from the server, I'm having trouble using the .subscribe() method to get a useful set of data out of it.
Here's my service which connects to the node server:
#Injectable()
export class TradeService {
private url = '...'; // URL to web API
tradeArray: Trade[] = [];
constructor(private http: Http) { }
//-----------GETTERS---------------//
getTradeObservable(): Observable<Trade> {
return this.http.get(this.url)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
console.log("body:" + body);
console.log("Entire Body.trades: " + body.trades);
return body.trades;
}
getTrades(): any {
this.getTradeObservable()
.subscribe(
trade => this.tradeArray.push(trade));
return this.tradeArray;
}
And here are the relevant portions the node server itself:
var TRADES = { "trades": [
{"id": 0, "cust": "Ben", "hasSub": true,
"subcust": "Rigby", "type": "s", "security": "001", "ticket": "99"},
...
{"id": 9, "cust": "Uber Bank", "hasSub": true,
"subcust": "Lil Bank", "type": "p", "security": "456", "ticket": "56"}
]};
////////////Get Requests/////////////////
//this route returns all data in JSON format
app.get('/', function(req, res) {
res.send(JSON.stringify(TRADES));
});
And the expected output from getTrades:
[
{id: 0, cust: "Ben", hasSub: true,
subCust: "Rigby", type: "s", security: '001', ticket: '99'},
...
{id: 9, cust: "Uber Bank", hasSub: true,
subCust: "Lil' Bank", type: "p", security: '456', ticket: '56'},
];
And one of the places the service is injected into and called:
export class SubmittedComponent {
constructor(private tradeService: TradeService) { }
//initally show all trades
rows = this.tradeService.getTrades();
...
I can see in the browser console that 'entire body.trades' is a full list of the data I want, but it seems subscribe is not pushing them into tradeArray, which ends up undefined.
Thank you for your time.
So I suppose that you are calling getTrades() from one of your components. If this is the case, this is what will happen:
The request will be sent
The request will be processed in the background asynchronously
The method will not wait for the request to be resolved and will return the current value of tradeArray, which is []
To avoid this, you could refactor you components so that they invoke the getTradeObservable() method an subscribe to the returned Observable.
UPDATE: Another option would be to refactor you service to use a Subject', and expose it to your components through anObservable`.
UPDATE: Assuming that you have the following definition for Trade
export interface Trade{
id: number;
cust: string;
hasSub: boolean;
subCust: string;
type: string;s
security: string;
ticket: string;
}
You could try the following approach
class TestComponent {
data: Trade[];
// inject service in component
getData(){
this.service.getTradesObservable().subscribe(data => this.data = data);
}
}
And change the definition of getTradesObservable to :
getTradeObservable(): Observable<Trade[]> {
return this.http.get(this.url)
.map(this.extractData)
.catch(this.handleError);
}
Speaking just about this portion of the code:
getTrades(): any {
this.getTradeObservable()
.subscribe(
trade => this.tradeArray.push(trade));
return this.tradeArray;
}
since getTradeObservable is asynchronous this line: return this.tradeArray; will (maybe) execute before the observable is resolved, you should remove getTrades method from your service and instead get a hold of the observable returned by getTradeObservable in your component and rather than expecting the whole thing to return the value you want, you should assign that value in the subscription like this:
#Component({
providers:[TradeService ]
})
export class myComponent{
trades:Trade[];
constructor(tradeService:TradeService){
tradeService.getTradeObservable().subscribe(tradeRes=>this.trades=tradeRes as Trade[]);
}
}