I am new to the AWS world and have been working on an Amplify project for a few months. Project uses Amplify front-end with Appsync / GraphQL and dynamodb all set up via the console in Cloud9. Read access to the backend using the graphql queries works great but I simply cannot get any of the auto-generated mutations to work. All throw ""Request aborted" errors with ""Invalid URI format".
Here's a snippet of the error
Here's a simple example from my code:
import { createCategory} from './graphql/mutations';
//Here's the start of our Javascrpt App
const WriteTestPage = () => {
//define function to handle submitButton, which is used to create a new customer
async function handleSubmit () {
try {
//For Customers, the name is required
console.log('Called our API - top');
const categoryDetails = {
description: 'My new category'
}
//Call GraphQL with correct parameters to create in backend
const returnedCategory = await API.graphql(graphqlOperation(createCategory, { input: categoryDetails}));
console.log('Called our API');
} catch (err) {
console.log('error creating category', err);
}
}
Here's the corresponding schema. I've opened up all access and have a user pool as well as an API key.
type Category
#model
#auth(
rules: [
{ allow: groups, groups: ["superAdmin"]} #allow the admin to crud the table
{ allow: private, operations: [read, update, create, delete]} #allow the authenticated users to read data from the table
{ allow: public, operations: [read, update, create, delete]} #DEV only - allow the UNauthenticated users to read data from the table
])
{
id: ID!
description: String!
conectivity: [Conectivity] #connection(keyName: "byCategory", fields: ["id"]) #Every Category may have zero or more conectivities
}
Here's the network trace. Doesn't appear that the front-end is even issuing a POST to the backend.
Network trace
First post on StackOverflow so appologies for any errors. Any help would be much appreciated as I'm beating my head against the wall here. I'm sure it's something simple that I'm overlooking.
Full code illustrating error:
import './App.css';
//context testing and dev
import React, { useEffect, useState } from 'react';
import Amplify, { AUTH, API, graphqlOperation } from 'aws-amplify';
import { Link } from 'react-router-dom';
//Queries and Mutations
import { createCategory} from './graphql/mutations';
import {AmplifyAuthenticator, AmplifySignOut } from '#aws-amplify/ui-react';
//Here's the start of our Javascrpt App
const WriteTestPage = () => {
//define function to handle submitButton, which is used to create a new customer
async function handleSubmit () {
try {
//For Customers, the name is required
console.log('Called our API - top');
const categoryDetails = {
description: 'My new catetory'
}
//Call GraphQL with correct parameters to create in backend
const returnedCategory = await API.graphql(graphqlOperation(createCategory, { input: categoryDetails}));
console.log('Called our API');
} catch (err) {
console.log('error creating category', err);
}
}
//Define HTML to return in order to create page
return (
<div className="App">
<section>
<AmplifyAuthenticator>
<section>
<h3 className="sectionHeader">Write Test</h3>
<center><form onSubmit={handleSubmit}>
<div >
<table className="tableStyle">
<thead>
<tr>
<th className="tableHeaderStyle">Write Test</th>
</tr>
</thead>
<tbody>
<tr className="tableRowOddStyle">
<td className="tableDataStyle">
<button className="btn" type="submit" className="buttonStyle">Add</button>
</td>
</tr>
</tbody>
</table>
</div>
</form></center>
<p></p>
</section>
</AmplifyAuthenticator>
</section>
</div>
);
}
export default WriteTestPage;
Related
i am developing a project using DRF and react js not delete using drf working fine but by axios.delete giving 403 error.
actions/review.js
export const deleteReview = (id) => dispatch => {
axios
.delete(`/api/review/${id}`)
.then(res => {
dispatch({
type: DELETE_REVIEW,
payload: id
})
})
.catch(err => console.log(err))
}
reducers/review.js
case DELETE_REVIEW:
return {
...state,
review: state.review.filter(review => review.id !== action.payload)
}
main review file
<tbody>
{ this.props.review.map(review => (
<tr key={review.id}>
<td>{review.id}</td>
<td>{review.city_name}</td>
<td>{review.traveller_name}</td>
<td>{review.traveller_review}</td>
<td>{review.review_img}</td>
<td><button onClick = {this.props.deleteReview.bind(this, review.id)}className="btn btn-danger btn-sm">Delete</button></td>
</tr>
</tbody>
I have a simple application form which I am trying to post to the server. I am fairly new to Angular2
How can I pass the data from the component to the service and onto the server for a POST request.
The POST is working fine when I try it directly from FireFox plugin 'httpRequester'
This is the TaskComponent.ts
#Component({
selector: 'tasks',
template: `<div mdl class="mdl-grid demo-content">
<div class="demo-graphs mdl-shadow--2dp mdl-color--white mdl-cell mdl-cell--8-col">
<h3>Create Task Page</h3>
<form action="#" (ngSubmit)="onSubmit()">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input class="mdl-textfield__input" type="text" pattern="[A-Z,a-z]*" id="taskname" [(ngModel)]="data.taskname"/>
<label class="mdl-textfield__label" for="taskname">Task Name</label>
<span class="mdl-textfield__error">Only alphabet and no spaces, please!</span>
</div>
<button class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored" type="submit">Create Task</button>
</form>
`,
directives: [ROUTER_DIRECTIVES, MDL]
})
export class CreateTaskComponent {
data: any
constructor() {
this.data = {
//taskname: 'Example Task'
};
}
onSubmit(form) {
console.log(this.data.taskname); <--Data is passed upon submit onto the console. Works fine.
//Need to call the postApartment method of ApartmentService
}
}
ApartmentService.ts
import {Http, Response} from 'angular2/http'
import {Injectable} from 'angular2/core'
import 'rxjs/add/operator/map';
#Injectable()
export class ApartmentService {
http: Http;
constructor(http: Http) {
this.http = http;
}
getEntries() {
return this.http.get('./api/apartments').map((res: Response) => res.json());
}
getProfile(userEmail :string){
return this.http.get(`./api/apartments/getprofile/${userEmail}`).map((res: Response) => res.json());
}
postApartment(){
// Not familiar with the syntax here
}
}
Server.ts
router.route('/api/apartments')
.post(function(req, res) {
var apartment = new Apartment();
apartment.name = req.body.name;
apartment.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'Apartment created!' });
});
})
You can inject service via dependency injection and use it in the component
export class CreateTaskComponent {
constructor(){private _apartmentService: ApartmentService}{}
}
And you can access this in any of the component function via
onSubmit(form) {
console.log(this.data.taskname); <--Data is passed upon submit onto the console. Works fine.
//Need to call the postApartment method of ApartmentService
this._apartmentService.postApartment()
}
And when bootstraping the component you have to add it as dependency via
bootstrap(AppComponent, [ApartmentService]);
Another option for doing the last step is by added providers in the Component decorator like
#Component{
providers: [ApartmentService]
}
Inject the apartmentService in the component, No need of providers as I have bootstrapped it. (If you bootstartp the service, Do not include it in providers. It breaks the system)
export class CreateTaskComponent {
data: any
constructor(private apartmentService: ApartmentService) {
this.data = {};
}
onSubmit(form) {
this.apartmentService.postApartment(this.data);
}
}
The critical piece is the postApartment() method in the service
postApartment(data :any){
return this.http.post('/api/apartments',
JSON.stringify(data),{headers : new Headers({'Content-Type':'application/json'})
})
.map((res: Response) => res.json()).subscribe();
}
Also make sure on the server.js code, the mongoose fields match the http body parameters being passed.
I had to fix it to make it work.
I would like to display an error message when the server responses with record not found.
The model in the route handler:
model: function(userLoginToken) {
var userLoginToken= this.store.createRecord('userLoginToken');
return userLoginToken;
},
The action:
actions: {
sendOTP: function(userLoginToken) {
var thisObject = this;
var model=this.currentModel;
this.store.findRecord('user-login-token', userLoginToken.get('mobileNumber')).then(function(response) {
//thisObject.get('controller').set('model', response);
},
function(error) {
//thisObject.get('controller').set('model', error);
//alert("model======== "+model.get('errors'));
});
},
The template is not displaying any error message.
The template:
{{#each model.errors.messages as |message|}}
<div class="errors">
{{message}}
</div>
{{/each}}
Unfortunately, the error message doesn't appear.
Ember depends on an DS.error object, in order to get errors from your models the response has to fulfill the requirements. In order to get Ember to recognize an valid error, in Ember 2.x the error code MUST be 422 and has to follow jsonapi http://jsonapi.org/format/#errors-processing
If you want to catch the errors from the backend response you have to use the catch method:
this.store.findRecord('user-login-token', userLoginToken.get('mobileNumber'))
.then(success => {
// Do whatever you need when the response success
})
.catch(failure => {
// Do whatever you need when the response fails
})
},
For catching the errors automatically as you are doing in your template, your backend needs to response in the right way. I would suggest you to read the answer for this SO question.
I have an Angular 2 service that has a logout function. When the function is called from the app component it cause a full page refresh. When using angular 1 projects I haven't experienced this behavior. If I call my logout endpoint with postman the session cookie is deleted. It is not deleted if I use my angular 2 authentication service.
Service
import {Injectable} from 'angular2/core';
import {User} from './user';
import {Headers, RequestOptions, Http, Response} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
import {Cookie} from '../extensions/cookies';
#Injectable()
export class AuthenticationService {
constructor (private http: Http) {}
private _prepTestHost = 'http://localhost:8000/';
private _prepTestLoginUrl = this._prepTestHost + 'login/';
private _prepTestLogoutUrl = this._prepTestHost + 'logout/';
private _authenticated: boolean;
getUser() {}
isAuthenticated() {
return this._authenticated;
}
setAuthenticated() {
this._authenticated = true;
}
loginUser(username, password) : Observable<User> {
let body = JSON.stringify({username, password});
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.post(this._prepTestLoginUrl, body, options)
.map(res => <User> res.json(), this.setAuthenticated())
.catch(this.handleError)
}
logoutUser() : Observable<void> {
let body = JSON.stringify({});
let csrfcookie = Cookie.getCookie('csrftoken');
let headers = new Headers({
'X-CSRFToken': csrfcookie,
'Content-Type': 'application/json'
});
let options = new RequestOptions({ headers: headers});
return this.http.post(this._prepTestLogoutUrl, body, options)
.map(res => <void> res.json())
.catch(this.handleError);
}
private handleError (error: Response) {
// in a real world app, we may send the server to some remote logging infrastructure
// instead of just logging it to the console
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}
App Component
import {Component} from 'angular2/core';
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {WelcomeCenterComponent} from './welcome-center/welcome-center.component';
import {AuthenticationService} from './authentication/authentication.service';
import {LoginModalComponent} from './authentication/login-modal.component';
import {BrowserXhr, HTTP_PROVIDERS} from "angular2/http";
import {CORSBrowserXHR} from './extensions/corsbrowserxhr';
import {provide} from "angular2/core";
#Component({
selector: 'my-app',
template: `
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#" [routerLink]="['WelcomeCenter']">Brand</a>
</div>
<ul class="nav navbar-nav navbar-right">
<li *ngIf="!authenticated()">
Login
</li>
<li *ngIf="authenticated()">
Logout
</li>
</ul>
</div>
</nav>
<router-outlet></router-outlet>
<login-modal></login-modal>
`,
directives: [ROUTER_DIRECTIVES, LoginModalComponent],
providers: [HTTP_PROVIDERS,
provide(BrowserXhr, {useClass: CORSBrowserXHR}),
AuthenticationService]
})
#RouteConfig([
{
path: '/welcome-center/...',
name: 'WelcomeCenter',
component: WelcomeCenterComponent,
useAsDefault: true
}
])
export class AppComponent {
constructor(private _authenticationService: AuthenticationService) {}
authenticated() {
return this._authenticationService.isAuthenticated();
}
logout() {
console.log("Logout button pressed");
this._authenticationService.logoutUser().subscribe();
}
}
Setting withCredentials attribute:
import {BrowserXhr, HTTP_PROVIDERS} from "angular2/http";
import {Injectable, provide} from "angular2/core";
#Injectable()
export class CORSBrowserXHR extends BrowserXhr{
build(): any{
var xhr:any = super.build();
xhr.withCredentials = true;
return xhr;
}
}
I think that the page reload is due to the fact that you don't prevent event propagation when blocking on the layout button (you an 'a' HTML element with an 'href' attribute). You could use 'return false' at the end of your logout function or '$event.stopPropagation()'.
See the question for more details:
Stop event propagation in Angular 2
Regarding the cookie problem, I these that you use cross domain requests (CORS). I think that you should try to set to true the 'withCredentials' attribute on the underlying XHR object. See this question for more details:
Set-cookie in response not set for Angular2 post request
You could do something kind of hacky but I can't think of another way.
Cookie.setCookie(nameOfCookie, "", -1);
This would effectively delete the cookie on logout. I'd love to know if there was a better way though!
I also am not sure why you are getting any kind of page reload at all, I have yet to experience that on anything I've done yet, hopefully someone else will know.
How should a template lookup the user access to a specific route before displaying a link/action?
Considering the routes already contain a list of authorized roles, should a simple template helper/component lookup the view property and validates access?
( something like {{#if has-access-to 'items.new'}} ? )
Routes are currently "protected" using a simple ACL solution:
AclRouteMixin
import Ember from 'ember';
var accountTypes = {
1: 'member',
2: 'manager',
3: 'owner'
};
export default Ember.Mixin.create({
beforeModel: function beforeModel(transition) {
this._super(transition);
var accountType = this.get('session.accountType');
var role = accountTypes.hasOwnProperty(accountType) ? accountTypes[accountType] : 'unknown';
if (this.get('roles') && !this.get('roles').contains(role)) {
transition.abort();
this.transitionTo('unauthorized');
}
}
});
Route
export default Ember.Route.extend(AuthenticatedRouteMixin, AclRouteMixin, {
roles: [ 'manager', 'owner' ]
});
EDIT
Since the server knows the permissions it is much easier to include a policy object ( or per-entity properties ) than trying to duplicate the authorization logic.
This talk ( linked by MilkyWayJoe ) explains a really simple way to setup authentication / ACL.
The session object ( or each API response ) could contain a Policy object that contains true/false values.
Template
{{#if session.policy.canCreateItems}}{{link-to 'New Item' 'items.new'}}{{/if}}
{{#if item.policy.canEdit}}{{link-to 'Edit' 'items.edit' item}}{{/if}}
Authenticator ( if using ember-simple-auth )
var session = {
userId: response.user_id,
policy: {}
};
for(var p in response.policy) {
if (response.policy.hasOwnProperty(p)) {
session.policy[p.camelize()] = response.policy[p];
}
}
API responses
{
"items": [{
...
policy: {
can_delete: true,
can_view: true,
can_edit: true
}
}],
"policy": {
can_create: true
}
}
The way I would do it is load up the permissions on an auth route that all other routes extend, as for checking it and displaying links I went ahead with a component:
import Ember from 'ember';
var Component = Ember.Component;
export default Component.extend({
hasPermission: function() {
var permission = this.get('permission');
return this.get('auth.permissions').indexOf(permission) !== -1;
}.property('permission')
});
As for the template:
{{#if hasPermission}}
{{yield}}
{{/if}}
And simply call it from links:
{{#can-do permission="view_tables"}}
{{link-to "tables" "tables" class="nav__link"}}
{{/can-do}}
Hope it helps. Let me know if you have any questions.