I'm new in graphql and I am trying to integrate an authentication/authorization system in my project. I found an example on Medium, but I do not understand how a guard communicates with a resolver. If someone knows, I will be very grateful.
import { ApolloServer } from 'apollo-server';
import gql from 'graphql-tag';
import { tradeTokenForUser } from './auth-helpers';
const HEADER_NAME = 'authorization';
const typeDefs = gql`
type Query {
me: User
serverTime: String
}
type User {
id: ID!
username: String!
}
`;
const resolvers = {
Query: {
me: authenticated((root, args, context) => context.currentUser),
serverTime: () => new Date(),
},
User: {
id: user => user._id,
username: user => user.username,
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
let authToken = null;
let currentUser = null;
try {
authToken = req.headers[HEADER_NAME];
if (authToken) {
currentUser = await tradeTokenForUser(authToken);
}
} catch (e) {
console.warn(`Unable to authenticate using auth token: ${authToken}`);
}
return {
authToken,
currentUser,
};
},
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
export const authenticated = next => (root, args, context, info) => {
if (!context.currentUser) {
throw new Error(`Unauthenticated!`);
}
return next(root, args, context, info);
};
I do not understand what "next" parameter does and why as an argument when this guard is called I have to return a value?
authenticated is higher-order function that makes the code DRY. next is a callback that is used as a predicate.
It's a DRYer way to write:
...
me: (root, args, context) => {
if (!context.currentUser) {
throw new Error(`Unauthenticated!`);
}
return context.currentUser;
)
...
Related
I'm trying to learn how to use firestore and I can't solve this error when trying to add a client:
(I'm using Next.Js, Typescript and Firebase 8.8.0)
TypeError: this._delegate.toFirestore is not a function
return client;
} else {
const docRef = await this.collection().add(client);
| ^
const doc = await docRef.get();
return doc.data() as Client;
}
I tried to use Firebase V9 but got the same error.
If I add clients collection manually in firebase page I get this error:
TypeError: this._delegate.fromFirestore is not a function
async showAll(): Promise<Client[]> {
const query = await this.collection().get()
return query.docs.map((doc: any) => doc.data()) ?? [];
^
}
My code:
import ClientRepository from "../../core/ClientRepository";
import Client from "../../core/Clients";
import firebase from "../config";
export default class ClientCollection implements ClientRepository {
#converter: any = {
toFireStore(client: Client) {
return {
name: client.name,
taxId: client.taxId,
ie: client.ie,
zipCode: client.zipCode,
state: client.state,
city: client.city,
district: client.district,
street: client.street,
number: client.number,
};
},
fromFireStore(snapshot: firebase.firestore.QueryDocumentSnapshot, options: firebase.firestore.SnapshotOptions): Client {
const data = snapshot.data(options);
return new Client(
data.name,
data.taxId,
data.ie,
data.zipCode,
data.state,
data.city,
data.district,
data.street,
data.number,
snapshot.id
);
},
};
private collection() {
return firebase.firestore().collection("clients").withConverter(this.#converter);
}
async save(client: Client): Promise<Client> {
if (client?.id) {
await this.collection().doc(client.id).set(client);
return client;
} else {
const docRef = await this.collection().add(client);
const doc = await docRef.get();
return doc.data() as Client;
}
}
async delete(client: Client): Promise<void> {
return this.collection().doc(client.id).delete();
}
async showAll(): Promise<Client[]> {
const query = await this.collection().get()
return query.docs.map((doc: any) => doc.data()) ?? [];
}
}
Im havving trouble building an express api in TS node.
Im new at express and im learning Node, JS and TS since 2022 so im sorry if the question is not to complex.
The thing is that im tryng to build a class Controller that handles the needed instructions for every route in my express router.
And im passing through the constructor a DAO that ive builded to access the firebase firestore database.
But when i instanciate the object and i try to run it Gives me the cannot read properties of undefined error.
Even when ive found a solution by using a clousure i want to learn how to do this using classes
Here it is the code of the DAO
import { setDoc, doc, getDocs, collection, query, where, deleteDoc } from 'firebase/firestore'
import { getStorage, ref, uploadBytes, getDownloadURL } from 'firebase/storage'
import { DataResponse, GenericItem } from '../types'
import { DAO } from '../clases/abstractClasses'
import { v4 } from 'uuid'
import db from '../config/firebase'
// import fs from 'fs/promises'
const fs = require('fs').promises
const storage = getStorage()
export class DataResponseClass implements DataResponse {
data: GenericItem[]
status: number
statusText: string
err: string
ok: boolean
constructor (data: GenericItem[], status: number, statusText: string, err: string, ok: boolean) {
this.data = data
this.status = status
this.statusText = statusText
this.err = err
this.ok = ok
}
}
export class DbManager extends DAO {
constructor (collectionRef: string) {
super(collectionRef)
}
async addItem (item: GenericItem): Promise<DataResponse> {
const id = v4()
console.log(id, typeof id)
return await setDoc(doc(db, this.collectionRef, id), { ...item, id }).then(res => {
return new DataResponseClass([{ ...item, id }], 201, 'Item added successfully', '', true)
}).catch(err => {
return new DataResponseClass([item], 400, "Couldn't add item", err.toString(), false)
})
}
async getAll (): Promise<DataResponse> {
return await getDocs(collection(db, this.collectionRef)).then(response => {
const dataArray: any = []
response.forEach(item => dataArray.push(item.data()))
return new DataResponseClass(dataArray, 200, 'Information obtained', '', true)
}).catch(err => new DataResponseClass([], 400, 'Couldnt Retrieve data', err.toString(), false))
}
async getById (passedId: string): Promise<DataResponse> {
const q = query(collection(db, this.collectionRef), where('id', '==', passedId))
return await getDocs(q)
.then(res => {
const dataArray: any[] = []
res.forEach(item => {
dataArray.push(item.data())
})
if (dataArray.length === 0) throw new Error('No data found for the id')
return new DataResponseClass(dataArray, 200, 'Information obtained', '', true)
})
.catch(err => new DataResponseClass([], 400, 'Couldnt Retrieve data', err.toString(), false))
}
async updateById (id: string, item: GenericItem): Promise<DataResponse> {
return await setDoc(doc(db, this.collectionRef, id), item)
.then(() => new DataResponseClass([{ ...item, id }], 200, 'Item succesifuly updated', '', true))
.catch(err => new DataResponseClass([], 400, 'Couldnt update item', err.toString(), false))
}
async deleteByid (id: string): Promise<DataResponse> {
return await deleteDoc(doc(db, this.collectionRef, id))
.then(() => new DataResponseClass([], 200, 'Success deleting a document', '', true))
.catch(err => new DataResponseClass([], 400, 'Couldnt Delete data', err, false))
}
async upLoadFile (file: Express.Multer.File | undefined): Promise<string> {
if (file !== undefined) {
const buffer = await fs.readFile(file.path).then()
const reference = ref(storage, `/${this.collectionRef}/${file.filename}`)
try {
await uploadBytes(reference, buffer)
return await getDownloadURL(reference)
} catch (err: any) {
console.log(err)
return 'There was an error uploading the file'
}
}
return 'No file was uploaded'
}
}
And this is the Controller class code..
import colors from 'colors'
import { Request, Response } from 'express'
import { DbManager, DataResponseClass } from '../services/firebase'
import fs from 'fs/promises'
export class Controller {
protected readonly dbManager: DbManager
constructor (collection: string) {
this.dbManager = new DbManager(collection)
}
async readData (req: Request, res: Response): Promise<void> {
const id: string = req.params.id
if (id !== undefined) {
res.send(await this.dbManager.getById(id))
} else {
res.send(await this.dbManager.getAll())
}
}
async createData (req: Request, res: Response): Promise<void> {
if (req.file !== undefined) {
const uploadedFilePath = await this.dbManager.upLoadFile(req.file)
.then((response: any) => {
console.log(`${response}/${req.file?.filename || ' '}`)
if (req.file?.path !== undefined) {
fs.unlink(req.file.path).then(() => console.log('Upload Complete')).catch(err => console.log(err))
}
return `${response}`
})
.catch((err: any) => {
console.log(err)
res.send(false)
})
const data = { ...req.body, images: uploadedFilePath }
console.log(colors.bgRed.white(data))
res.send(await this.dbManager.addItem({ ...req.body, images: uploadedFilePath }))
} else res.send(new DataResponseClass([], 400, 'Invalid Request no image uploaded', 'Invalid Request no image uploaded', false))
}
async editData (req: Request, res: Response): Promise<void> {
const { id } = req.params
if (req.file !== undefined) {
const uploadedFilePath = await this.dbManager.upLoadFile(req.file)
.then((response: any) => {
if (req.file?.path !== undefined) {
fs.unlink(req.file.path).then(() => console.log('Upload Complete')).catch(err => console.log(err))
}
return `${response}`
})
.catch((err: { toString: () => string }) => {
console.log(err)
res.send(new DataResponseClass([], 400, 'Imposible to upload the file', err.toString(), false))
})
res.send(await this.dbManager.updateById(id, { ...req.body, images: uploadedFilePath }))
} else res.send(new DataResponseClass([], 400, 'Invalid Request no image uploaded', 'Invalid Request no image uploaded', false))
}
async deleteData (req: Request, res: Response): Promise<void> {
const { id } = req.params
if (id !== undefined) {
res.send(await this.dbManager.deleteByid(id))
} else res.send(new DataResponseClass([], 400, 'Invalid Request no id', 'Invalid Request no id', false))
}
}
Thanks for your time Im tryng to learn this beautifull world that is the backend development
Ive tryed to call the constructor outside the class and pass the constant to the constructor
Ive tryed to instanciate the DAO object as a param,Ive even called the dao constructor in a global variable and defined the properti taking value from it .
But the only solution ive found for my issue es transforming the class into a clousure function and calling the constructor in the body of the closure
TypeError: Cannot read properties of undefined (reading 'dbManager')
at /run/media/adrianabadin/code/dcsbackend/src/controllers/controllerClass.ts:20:27
at Generator.next (<anonymous>)
at /run/media/adrianabadin/code/dcsbackend/src/controllers/controllerClass.ts:8:71
at new Promise (<anonymous>)
at __awaiter (/run/media/adrianabadin/code/dcsbackend/src/controllers/controllerClass.ts:4:12)
at readData (/run/media/adrianabadin/code/dcsbackend/src/controllers/controllerClass.ts:24:16)
at Layer.handle [as handle_request] (/run/media/adrianabadin/code/dcsbackend/node_modules/express/lib/router/layer.js:95:5)
at next (/run/media/adrianabadin/code/dcsbackend/node_modules/express/lib/router/route.js:144:13)
at Route.dispatch (/run/media/adrianabadin/code/dcsbackend/node_modules/express/lib/router/route.js:114:3)
at Layer.handle [as handle_request] (/run/media/adrianabadin/code/dcsbackend/node_modules/express/lib/router/layer.js:95:5)
[ERROR] 16:55:20 TypeError: Cannot read properties of undefined (reading 'dbManager')
Routes
Here I call the method readData
import { Router } from 'express'
import { Controller } from '../controllers/controllerClass'
// import { Validation } from '../services/validation'
import { upload } from '../config/multer'
const router = Router()
// const { validate } = new Validation('welcome')
const { readData, createData, editData, deleteData } = new Controller('welcome')
router.get('/', readData)
router.get('/:id', readData)
router.post('/', upload.single('images'), createData)
router.put('/:id', upload.single('images'), editData)
router.delete('/:id', deleteData)
export default router
const { readData, createData, editData, deleteData } = new Controller('welcome')
You can't destructure normally declared instance methods from classes.
I'm going to vastly simplify this to this example:
class Foo {
private data = 123
getData() { return this.data }
}
Now if you call getData like so, it works:
const foo = new Foo()
console.log(foo.getData())
// 123
But if you destructure the method:
const { getData } = new Foo()
console.log(getData())
// Cannot read properties of undefined (reading 'data')
Then it crashes.
The value of this is being lost because you don't call it with a ., which is what provides the class instance to the function.
Now let's try this one:
class Foo {
private data = 123
getData = () => { return this.data }
}
const foo = new Foo()
console.log(foo.getData())
// 123
const { getData } = new Foo()
console.log(getData())
// 123
This works because typescript compiles property assignments in classes to happen in the constructor, and because the arrow function => captures the value of this from when it was declared. So now you can break off the method and it works.
Just note that while the traditional instance method declaration is shared between all instances, this arrow function method will create a new function for every instance. This may hurt performance if you plan to create a very large number of instances. But for backend service classes like this that's probably not a concern.
So in your case just call the method on the instance:
const controller = new Controller('welcome')
router.get('/', (req, res) => controller.readData(req, res))
Or you declare your method as a arrow function.
export class Controller {
//...
readData = async (req: Request, res: Response): Promise<void> => {
const id: string = req.params.id
if (id !== undefined) {
res.send(await this.dbManager.getById(id))
} else {
res.send(await this.dbManager.getAll())
}
}
//...
}
const { readData } = new Controller()
readData() // fine now
I'm trying to create a user after he verified the code that I send him
so first I generate the code in sendCode resolver and save it in Redis using setex
the problem is that code is set in Redis but when I try to use it in createUser resolver using get it returns null.
const sendCode: MutationResolvers["sendCode"] = async ({
input: { phoneNumber, email },
}: {
input: SendCodeInput;
}) => {
const code = generate4digitNum();
await redis.setex(phoneNumber ?? email, THREE_MINS, code);
return {};
};
const createUser: MutationResolvers["createUser"] = async ({
input: { ...userData },
}: {
input: CreateUserInput;
}) => {
const code = await redis.get(userData.phoneNumber ?? userData.email);
if (code !== userData.code) {
throw new Error(errors[0].id);
}
user = await userModel.create({ ...userData});
return {type: user.type, _id: user._id };
};
the redis.ts file that I create:
const client = redis.createClient({
host: process.env.REDIS_HOST,
password: process.env.REDIS_PASSWORD,
port: Number(process.env.REDIS_PORT),
});
client
.on("connect", function () {
console.log(`connected ${client.connected}`);
})
.on("error", function (error) {
console.log(error);
});
export const get: (key: string) => Promise<string> = promisify(client.get).bind(
client
);
export const setex: (
key: string,
seconds: number,
value: string
) => Promise<string> = promisify(client.setex).bind(client);
I will appreciate any kind of help.
Thanks in advance.
I'm coding an Apollo server.
The Query work very well but when I'm trying to make a mutation it returns the Models.create(...).exec() is not a function. The new data I pass in gets created.
I've been trying to fix it for hours, and I don't know where I'm doing wrong
Here is my resolver
const { Models } = require('./dataSource/models')
const resolvers = {
Query: {
getPerson: async (_, args) => {
let response = await Models.find(args)
return response
}
},
Mutation: {
addPerson: async (_, args) => {
try{
let response = await Models.create(args).exec()
return response
}
catch(err){
return console.log(err)
}
}
}
}
module.exports = resolvers
and Here the model
const mongoose = require('mongoose')
const { Schema, model } = mongoose;
const PersonSchema = new Schema({
gender: String,
age: Number
});
const Models = model('person', PersonSchema)
module.exports = { Models }
Many thanks for your help
I have a simple class-based component that I'm trying to convert to a function-based component, but am running into all kinds of dead ends.
My component is a straightforward adaptation of the boilerplate gifted-chat package, and uses Watson Assistant as a backend to provide responses. There's nothing complex about the backend part, these are just thin wrappers on Watson Assistants's API:
getSessionID = async (): Promise<string>
gets a session ID for use in communicating with the backend, and
sendReply = async (reply: string, sessionID: string): Promise<string>
returns Assistant's response to the string provided as a reply. These are not the source of the trouble I'm having (the bodies of both could be replaced with return await "some string" and I'd have the same issues): the class-based version (below) works perfectly.
But I'm at a loss to figure out how to convert this to a functional form, in particular:
I'm struggling to find a suitable replacement for componentWillMount. Using useEffect with sessionID as state results in errors: getMessage gets called (even if I await) before the required sessionID is set.
I can avoid this by not making sessionID state (which it arguably shouldn't be) and just making it a global (as in the functional attempt below). But even if I do this:
After each user reply, and receipt of a response, the user reply is removed from the conversation, so that the entire conversation just consists of generated replies.
Both of these problems are, I think, linked to the lack of callbacks in the hook-based state setting idiom, but the issue could also lie elsewhere. In any case, I'm at a loss to know what to do.
Chatter.tsx (working class based version)
import React from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
class Chatter extends React.Component {
state = {
messages: [],
sessionID: null,
}
componentWillMount() {
WatsonAssistant.getSessionID()
.then((sID) => {
this.setState( {
sessionID: sID,
} )
} )
.then(() => this.getMessage(''))
.catch((error) => {
console.error(error)
} )
}
onSend = (message = []): void => {
this.setState((previousState) => ( {
messages: GiftedChat.append(previousState.messages, message),
} ), () => {
this.getMessage(message[0].text.replace(/[\n\r]+/g, ' '))
} )
}
getMessage = async (text: string): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, this.state.sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
this.setState((previousState) => ( {
messages: GiftedChat.append(previousState.messages, message),
} ))
}
render() {
return (
<GiftedChat
messages={ this.state.messages }
onSend={ messages => this.onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
}
export default Chatter
Chatter.tsx (failed function based attempt)
import React, {FC, ReactElement, useEffect, useState } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
let sessionID: string
const Chatter: FC = (): ReactElement => {
const [ messages, setMessages ] = useState([])
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => sessionID = sID )
.then(() => getMessage(''))
.catch((error) => {
console.error(error)
} )
}
fetchData()
}, [ ])
const onSend = async (message = []) => {
const newMessages = await GiftedChat.append(messages, message)
await setMessages(newMessages)
await getMessage(message[0].text.replace(/[\n\r]+/g, ' '))
}
const getMessage = async (text: string): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
await setMessages(await GiftedChat.append(messages, message))
}
return (
<GiftedChat
messages={ messages }
onSend={ messages => onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
export default Chatter
Chatter.tsx (working function based version)
import React, {FC, ReactElement, useEffect, useState } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
let sessionID: string
const Chatter: FC = (): ReactElement => {
const [ messages, setMessages ] = useState([])
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => sessionID = sID )
.then(() => getMessage('', []))
.catch((error) => {
console.error(error)
} )
}
fetchData()
}, [ ])
const onSend = async (message = []) => {
const newMessages = await GiftedChat.append(messages, message)
await setMessages(newMessages) // Apparently, no waiting goes on here
await getMessage(message[0].text.replace(/[\n\r]+/g, ' '), newMessages)
}
const getMessage = async (text: string, currentMessages): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
await setMessages(await GiftedChat.append(currentMessages, message))
}
return (
<GiftedChat
messages={ messages }
onSend={ messages => onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
export default Chatter
Ok, since I don't have your full code I'm not sure this will just work as-is (in particular without the types from your dependencies I'm not sure if/how much the compiler will complain), but should give you something you can adapt easily enough.
const reducer = ({ messages }, action) => {
switch (action.type) {
case 'add message':
return {
messages: GiftedChat.append(messages, action.message),
};
case 'add sent message':
return {
// Not sure if .append is variadic, may need to adapt
messages: GiftedChat.append(messages, action.message, action.message[0].text.replace(/[\n\r]+/g, ' ')),
}
}
};
const Chatter = () => {
const [sessionID, setSessionID] = useState(null);
const [messages, dispatch] = useReducer(reducer, []);
const getMessage = async (text: string, sessionID: number, type: string = 'add message'): Promise<void> => {
const response = await WatsonAssistant.sendReply(text, sessionID);
const message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
};
dispatch({
type,
message,
});
};
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => (setSessionID(sID), sID))
.then(sID => getMessage('', sID))
.catch((error) => {
console.error(error)
});
}
fetchData();
}, []);
return (
<GiftedChat
messages={messages}
onSend={messages => getMessage(messages, sessionID, 'add sent message')}
user={{
_id: 1,
}}
/>
);
};
Main difference is useReducer. As far as I can tell in the original code you had two actions: append this message or append this message and then a copy of it with the text regex replaced. I've used different dispatches to the reducer to handle the cases rather than the callback to setState. I've modified your attempt at useEffect, here I'm (ab)using the comma operator to return the ID returned from the service so that it can be fed directly to getMessage as a parameter rather than relying on state that hasn't been updated yet.
I'm still kinda skeptical in general about the hooks API, but assuming this works I actually think it simplifies the code here.