React Admin how to resolve an url into a field - reactjs

I'm new with react admin and I'm tring to figure out how can I resolve an url in an Custom field. I am usin Django Rest Framework as my backend application and it returns this:
{
"id": "PwybRVej1r3L2Ag7smAvpqW45076GzZd",
"unity": "http://localhost:8000/api/v1/unity/n7VzbMW25rYZLB17SZ9Rl8eXqE36QDxk/",
"url": "http://localhost:8000/api/v1/truck/PwybRVej1r3L2Ag7smAvpqW45076GzZd/",
"created": "2022-08-08T23:48:32.876117Z",
"modified": "2022-08-08T23:48:32.876117Z",
"license_plate": "ADS-8974"
},
And I'm using JWT as the authentication standard. In this way I found ra-data-django-rest-framework. For simple cases, the usage is simply straightforward. But I'm trying to create a field that can resolve the url("http://localhost:8000/api/v1/unity/n7VzbMW25rYZLB17SZ9Rl8eXqE36QDxk/") and returns the information I need. Does anyone know how it could be resolved?
I tried this without success:
import * as React from "react";
import { useRecordContext, useGetOne } from 'react-admin';
const UnityField = ({ source }) => {
const record = useRecordContext();
const unity = useGetOne(record[source]);
console.log(unity)
return record ? (
<a href={record[source]}>
{unity}
</a>
) : null;
}
export default UnityField;

React-admin provides an <UrlField>, does it work in your case?
<UrlField source="unity" />
See https://marmelab.com/react-admin/UrlField.html for details

Related

Uppy/Shrine: How to retrieve presigned url for video after successful upload (using AWS S3)

I'm using Uppy for file uploads in React, with a Rails API using Shrine.
I'm trying to show a preview for an uploaded video before submitting a form. It's important to emphasize that this is specifically for a video upload, not an image. So the 'thumbnail:generated' event will not apply here.
I can't seem to find any events that uppy provides that returns a cached video preview (like thumbnail:generated does) or anything that passes back a presigned url for the uploaded file (less expected, obviously), so the only option I see is constructing the url manually. Here's what I'm currently trying for that (irrelevant code removed for brevity):
import React, { useEffect, useState } from 'react'
import AwsS3 from '#uppy/aws-s3'
import Uppy from '#uppy/core'
import axios from 'axios'
import { DragDrop } from '#uppy/react'
import { API_BASE } from '../../../api'
const constructParams = (metadata) => ([
`?X-Amz-Algorithm=${metadata['x-amz-algorithm']}`,
`&X-Amz-Credential=${metadata['x-amz-credential']}`,
`&X-Amz-Date=${metadata['x-amz-date']}`,
'&X-Amz-Expires=900',
'&X-Amz-SignedHeaders=host',
`&X-Amz-Signature=${metadata['x-amz-signature']}`,
].join('').replaceAll('/', '%2F'))
const MediaUploader = () => {
const [videoSrc, setVideoSrc] = useState('')
const uppy = new Uppy({
meta: { type: 'content' },
restrictions: {
maxNumberOfFiles: 1
},
autoProceed: true,
})
const getPresigned = async (id, type) => {
const response = await axios.get(`${API_BASE}/s3/params?filename=${id}&type=${type}`)
const { fields, url } = response.data
const params = constructParams(fields)
const presignedUrl = `${url}/${fields.key}${params}`
console.log('presignedUrl from Shrine request data: ', presignedUrl)
setVideoSrc(presignedUrl)
}
useEffect(() => {
uppy
.use(AwsS3, {
id: `AwsS3:${Math.random()}`,
companionUrl: API_BASE,
})
uppy.on('upload-success', (file, _response) => {
const { type, meta } = file
// First attempt to construct presigned URL here
const url = 'https://my-s3-bucket.s3.us-west-1.amazonaws.com'
const params = constructParams(meta)
const presignedUrl = `${url}/${meta.key}${params}`
console.log('presignedUrl from upload-success data: ', presignedUrl)
// Second attempt to construct presigned URL here
const id = meta.key.split(`${process.env.REACT_APP_ENV}/cache/`)[1]
getPresigned(id, type)
})
}, [uppy])
return (
<div className="MediaUploader">
<div className="Uppy__preview__wrapper">
<video
src={videoSrc || ''}
className="Uppy__preview"
controls
/>
</div>
{(!videoSrc || videoSrc === '') && (
<DragDrop
uppy={uppy}
className="UploadForm"
locale={{
strings: {
dropHereOr: 'Drop here or %{browse}',
browse: 'browse',
},
}}
/>
)}
</div>
)
}
export default MediaUploader
Both urls here come back with a SignatureDoesNotMatch error from AWS.
The manual construction of the url comes mainly from constructParams. I have two different implementations of this, the first of which takes the metadata directly from the uploaded file data in the 'upload-success' event, and then just concatenates a string to build the url. The second one uses getPresigned, which makes a request to my API, which points to a generated Shrine path that should return data for a presigned URL. API_BASE simply points to my Rails API. More info on the generated Shrine route here.
It's worth noting that everything works perfectly with the upload process that passes through Shrine, and after submitting the form, I'm able to get a presigned url for the video and play it without issue on the site. So I have no reason to believe Shrine is returning incorrectly signed urls.
I've compared the two presigned urls I'm manually generating in the form, with the url returned from Shrine after uploading. All 3 are identical in structure, but have different signatures. Here are those three urls:
presignedUrl from upload-success data:
https://my-s3-bucket.s3.us-west-1.amazonaws.com/development/cache/41b229fb17cbf21925d2cd907a59be25.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAW63AYCMFA4374OLC%2F20221210%2Fus-west-1%2Fs3%2Faws4_request&X-Amz-Date=20221210T132613Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=97aefd1ac7f3d42abd2c48fe3ad50b542742ad0717a51528c35f1159bfb15609
presignedUrl from Shrine request data:
https://my-s3-bucket.s3.us-west-1.amazonaws.com/development/cache/023592fb14c63a45f02c1ad89a49e5fd.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAW63AYCMFA4374OLC%2F20221210%2Fus-west-1%2Fs3%2Faws4_request&X-Amz-Date=20221210T132619Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=7171ac72f7db2b8871668f76d96d275aa6c53f71b683bcb6766ac972e549c2b3
presigned url displayed on site after form submission:
https://my-s3-bucket.s3.us-west-1.amazonaws.com/development/cache/41b229fb17cbf21925d2cd907a59be25.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAW63AYCMFA4374OLC%2F20221210%2Fus-west-1%2Fs3%2Faws4_request&X-Amz-Date=20221210T132734Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=9ecc98501866f9c5bd460369a7c2ce93901f94c19afa28144e0f99137cdc2aaf
The first two urls come back with SignatureDoesNotMatch, while the third url properly plays the video.
I'm aware the first and third urls have the same file name, while the second url does not. I'm not sure what to make of that, though, but the relevance of this is secondary to me, since that solution was more of a last ditch effort anyway.
I'm not at all attached to the current way I'm doing things. It's just the only solution I could come up with, due to lack of options. If there's a better way of going about this, I'm very open to suggestions.

Uploading an image to Azure Blob Storage using React

I want to upload an image to Azure Blob Storage using React.
I've tried a lot of examples and none of them work.
The one that seemed the best was this one but still didn't manage to get it working on React.
What I'm trying right now is to use the createContainerIfNotExists method just to test and the error is Cannot read property createBlobServiceWithSas of undefined
My code is the following:
import AzureStorage from 'azure-storage';
const account = {
name: 'x',
sas: 'x',
};
const blobUri = `https://${account.name}.blob.core.windows.net`;
const blobService = AzureStorage.Blob.createBlobServiceWithSas(blobUri, account.sas);
export const createContainer = () => {
blobService.createContainerIfNotExists('test', (error, container) => {
if (error) {
// Handle create container error
} else {
console.log(container.name);
}
});
};
export default createContainer;
According to my research, because you develop A React application, we can not use the createBlockBlobFromBrowserFile method. We just can use the method in the browser. For more details, please refer to the document.
According to the situation, I suggest you use the other method(such as uploadStreamToBlockBlob) to upload image with V10 sdk. For more details, please refer to https://learn.microsoft.com/en-us/javascript/api/#azure/storage-blob/?view=azure-node-latest

Meteor React tutorial interacting with mongo not working

I have been trying for a while to learn how to build mobile apps with Javascript, and honestly I have no idea how anyone is able to do anything. Everything is broken. Every tutorial I've tried has failed to work for some bizarre reason. I am at my wits end.
I've finally decided to try and be even simpler, and just do the most basic tutorial I can find. What could go wrong. Well, it only took 3 pages of almost no code to completely stop working. I've done this, and I cannot insert anything to my db. My app fetches no data. When trying to add a new task, it gets added then disappears almost immediately, with a message stating insert failed: Method '/tasks/insert' not found (not even an error with some traceback).
The code really couldn't be simpler:
// imports/api/tasks.js
import { Mongo } from 'meteor/mongo';
export const Tasks = new Mongo.Collection('tasks');
// imports/ui/App.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { withTracker } from 'meteor/react-meteor-data'
import { Tasks } from '../api/tasks.js';
import Task from './Task.js';
// App component - represents the whole app
class App extends Component {
handleSubmit(event) {
event.preventDefault();
// find the text field via the react ref
const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
Tasks.insert({ text, createdAt: new Date() });
// Clear form
ReactDOM.findDOMNode(this.refs.textInput).value = '';
}
renderTasks() {
return this.props.tasks.map((task) => (
<Task key={task._id} task={task} />
));
}
render() {
return (
<div className="container">
<header>
<h1>Todo List</h1>
<form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
<input
type="text"
ref="textInput"
placeholder="Type to add new tasks"
/>
</form>
</header>
<ul>
{this.renderTasks()}
</ul>
</div>
);
}
};
export default withTracker(() => {
return {
tasks: Tasks.find({}).fetch(),
};
})(App);
What is wrong? What am I missing?
The tutorial is indeed out of date and should be updated.
Background
In June 2017 there was a big security issue with allow/deny identified and the feature has been blocked since then.
Meteor allowed you to define client collection, that automatically synced with the server when the methods insert, update, remove were called on the client.
In order to control the access permissions, the allow/deny feature was implemented.
Now without allow/deny you will get the insert failed: Method '/tasks/insert' not found when classing SomeCollectionOnClient.insert but since this feature is obsolete (you will even get a big warning when setting it up), you need to create a server side method and call it from the client in order resolve this issue:
On the server create this method and ensure it is in the import chain from server/main.js:
new ValidatedMethod({
name: 'tasks.insert',
validate(args) {
// better use simpl-schema here
if (!args.text || !args.createdAt) {
throw new Meteor.Error('incompleteArgs', 'args are incomplete')
}
},
run (args) {
// check user permissions...
return Tasks.insert({ text, createdAt })
}
})
In your client component you can then call it via:
// find the text field via the react ref
const text = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
Meteor.call('tasks.insert', {text, createdAt: new Date()}, (err, res) => {
// do something on err / on res
})
Note that this couples your component to the server side method and you may rather try to implement some containers for your pages that handle all the connection / pub-sub / method calling activity wile your components solely render content.
More to read / used in this answer:
https://guide.meteor.com/react.html
https://guide.meteor.com/security.html
https://docs.meteor.com/api/methods.html#Meteor-call
https://guide.meteor.com/methods.html#validated-method

React Google API

I am new to React and RN. I have looked into every single solution here but I did not find a solution for my case. I am trying to pull google calendar events from calendar v3 api. I have tried two ways, so far. I don't know which one is correct but I did not get a correct result for any of them. Firstly, I have tried to send a request to the https://www.googleapis.com/calendar/v3/calendars/${CALENDAR_ID}/events?key=${API_KEY}( I don't know if the key parameter is needed. I think we should delete key parameter in front of the api key.I did it like that because otherwise it was giving an error as global not found).
This is calendar.js
const CALENDAR_ID = 'public#qeqw'
const API_KEY = 'key'
let url = `https://www.googleapis.com/calendar/v3/calendars/${CALENDAR_ID}/events?key=${API_KEY}`
export function getEvents (callback) {
request
.get(url)
.end((err, resp) => {
if (!err) {
const events = []
JSON.parse(resp.text).items.map((event) => {
events.push({
start: event.start.date || event.start.dateTime,
end: event.end.date || event.end.dateTime,
title: event.summary,
})
})
callback(events)
}
})
}
This is app.js
import React from 'react'
import { render } from 'react-dom'
import { getEvents } from './gcal'
import { View, Text,
StatusBar,Image,AppRegistry,ScrollView,StyleSheet,Platform,FlatList} from
'react-native'
class App extends React.Component {
constructor () {
super()
this.state = {
events: []
}
}
componentDidMount () {
getEvents((events) => {
this.setState({events})
})
}
render () {
return (
// React Components in JSX look like HTML tags
<View>
<Text>{this.state.events}</Text>
</View>
)
}
}
However, I got an error in the below. I don't know what I am doing wrong but it should be possible to send a request like that. My only concern is that if I need to get token by giving my client information by using OAuth2 authentication. Do I need to sign up and and get token to reach the API? If I need to do it, I have implemented to do it in node js by reading the sample here.https://developers.google.com/calendar/quickstart/nodejs but there are some node modules which I cannot use them in my React native application like fs, googleAuth, readline etc... Some of them can be done by using nodeify but others throw an error. So, I don't know what to do from now on. If someone can guide me how I would use google calendar api in react, I'd be appreciated. Thanks to the everyone who contributes here.
{
"error": {
"errors": [
{
"domain": "usageLimits",
"reason": "dailyLimitExceededUnreg",
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.",
"extendedHelp": "https://code.google.com/apis/console"
}
],
"code": 403,
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup."
}
}
As the error message indicates, you are beyond your usage for that API.
You need to sign up in order to continue to use the API.
Once you sign up, you can use a library like Request-Promise in order to make the API request.
OR
You can search npm for a react component that interfaces with the Google Calendar API, such as this one
The error is caused from an silly mistake. The key parameter in the url should not be in parenthesizes. The reason it did not work is that the parameter key which is in front of the api key is in parenthesize. If you delete it, it works like a charm.

How to set up admin-on-rest with couchDB?

Edit to the makers of AoR : Your framework suffers from horrid documentation. You should really focus on that, people would really adopt it then.
I cant for the life of me decipher how admin-on-rest does the 'rest' part. If there is a better framework with better documentation, Im open to that.
Im very new to react, so thats probably part of it.
What I can discern is that
1) The [Admin] tag takes a prop 'restClient', and this is a function that sets your base path to your JSON source, then returns a function with a specific signature (takes 3 arguments, returns a promise).
2) Then a [Resource] tag adds to the path with name="posts" and makes a list, which (heres where it turns to magic) basically does a wget to your database then iterates over the results.
What I want to do : hook up couchDB to admin-on-rest. I already have a few test docs made on localhost. The couchDB url looks like :
http://127.0.0.1:5984/myproject/_design/getclients/_view/getclient/
and this works in postman, giving me a json object like this :
{
"total_rows": 4,
"offset": 0,
"rows": [
{
"id": "afc3bb9218d1a5c1e81ab3cc9f004467",
"key": {
"status": "active",
"rating": 9.1,
"bio": {
"fname": "Sam",
"mname": "TestMName",
"lname": "TestLName",
"address": "712347 B Street",
"state": "CA",
"city": "Los Santos",
"zip": "90211",
"phone": "123-456-7890",
"email": "sam#samsemail.com",
"username": "TestSam",
"password": "abc123"
}
},
"value": null
},
At this point Im so confused I dont know where to look.
Heres my code now :
//App.js
import React from 'react';
import { jsonServerRestClient, Admin, Resource } from 'admin-on-rest';
import { PostList } from './Posts.js';
const App = () => (
<Admin restClient={jsonServerRestClient('http://127.0.0.1:5984/myproject/')}>
<Resource name="_design/getclients/_view/getclient" list={PostList} />
</Admin>
);
export default App;
And
//Posts.js
export const PostList = (props) => (
<List {...props}>
<Datagrid>
<TextField source="status" />
<TextField source="rating" />
</Datagrid>
</List>
);
The page loads but a little pink box pops up at the bottom saying :
The X-Total-Count header is missing in the HTTP Response. The jsonServer REST client expects responses
The RestClient is a bit of a murky beast. Not perfectly documented for sure.
But it is in the end quite straightforward if you know how the whole thing works together.
1) Admin-On-Rest has defined some REST types (below). These are usually shot off by Redux actions (in their meta tag). The system scans for these rest types and if it sees them, then it calls the RestClient
GET_LIST
GET_ONE
CREATE
UPDATE
DELETE
GET_MANY
GET_MANY_REFERENCE
The REST client is called with these types and some other params. It is the job of the rest client to interpret the type and then use the params to make a request to your API. For this AOR uses the new Fetch API that is built into browsers.
You can access it by calling. You should also go into AOR source code and check out how it works.
import { fetchUtils } from 'admin-on-rest';
2) The X total count is a header field that AOR needs for all responses to the GET_LIST type.
You can set this quite simply in your API. I use loopback and I set the X-Total-Count manually in a remote hook (don't worry about it if you don't know it)
It seems your api is still using the JSON server. JSON server is a dummy API. So your app is not connected to your couchDB right now.
https://github.com/typicode/json-server
If you are not using an api server like express or loopback, then you can also configure your restClient do all request and response handling. You have to construct the URL. Read the below link so you can follow my example code further down.
https://marmelab.com/admin-on-rest/RestClients.html#decorating-your-rest-client-example-of-file-upload
so something like this.
if (type === 'GET_LIST' && resource === 'posts') {
const url = http://127.0.0.1:5984/myproject/_design/getclients/_view/getclient/
options.method = 'GET';
return fetchUtils.fetchJson(url, options)
.then((response) => {
const {headers, json} = response;
//admin on rest needs the {data} key
return {data: json,
total: parseInt(headers.get('x-total-count').split('/').pop(), 10)}
})
You can also write a function like this to handle the request and response.
function handleRequestAndResponse(url, options={}) {
return fetchUtils.fetchJson(url, options)
.then((response) => {
const {headers, json} = response;
//admin on rest needs the {data} key
const data = {data: json}
if (headers.get('x-total-count')) {
data.total = parseInt(headers.get('x-total-count').split('/').pop(), 10)
} else {
data.total = json.length // this is why the X-Total-Count is needed by Aor
}
}
}
// handle get_list responses
return {data: json,
total: } else {
return data
}
})
}
The above code has been formatted in the window and so might not work straight out of the box. But I hope you get the idea.

Resources