I'm using a global interceptor to get response like:
{
"data": "",
"statusCode": int
"message": "string"
}
so I created the interceptor file
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "#nestjs/common";
import { map, Observable } from "rxjs";
export interface Response<T> {
data: T;
}
#Injectable()
export class TransformationInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(map(data => ({
data: data,
statusCode: context.switchToHttp().getResponse().statusCode,
message: data.message
})));
}
}
and put it into my main.ts
In my controller I have:
#Patch('/:userId')
#HttpCode(201)
public async updateUser(
#Param('userId') userId: string,
#Body() userUpdate: UpdateUserDto): Promise<any> {
return await this.usersService.update(userId, userUpdate);
}
and the result is:
{
"data": {
"_id": "621d07d9ea0cdc600fae0f02",
"username": "foo",
"name": "stringwwww",
"__v": 0
},
"statusCode": 201
}
If I want to add my custom message, I need to return an object like:
#Patch('/:userId')
#HttpCode(201)
public async updateUser(
#Param('userId') userId: string,
#Body() userUpdate: UpdateUserDto): Promise<any> {
const result = await this.usersService.update(userId, userUpdate);
return { message: 'User updated', result };
}
but in that case I have twice message and the structure is not correct:
{
"data": {
"message": "User updated",
"result": {
"_id": "621d07d9ea0cdc600fae0f02",
"username": "foo",
"name": "stringwwww",
"__v": 0
}
},
"statusCode": 201,
"message": "User updated"
}
How can I set a custom (optional) message?
I can modify my interceptors like:
#Injectable()
export class TransformationInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(map(data => ({
data: data.res,
statusCode: context.switchToHttp().getResponse().statusCode,
message: data.message
})));
}
}
and my controller like:
#Patch('/:userId')
#HttpCode(201)
public async updateUser(
#Param('userId') userId: string,
#Body() userUpdate: UpdateUserDto): Promise<any> {
const result = await this.usersService.update(userId, userUpdate);
return { message: 'User updated', res: result };
}
and I will get the correct form, but I don't want to add
return { message: 'User updated', res: result };
for each controller
One way to achieve this is as below, but you will be bound to a fixed message per controller
Create a response decorator (response.decorator.ts)
import { SetMetadata } from '#nestjs/common'
export const ResponseMessageKey = 'ResponseMessageKey'
export const ResponseMessage = (message: string) => SetMetadata(ResponseMessageKey, message)
Create a constants file for your responses (response.constants.ts)
export const USER_INSERTED = 'User Inserted'
export const USER_UPDATED = 'User Updated'
export const USER_DELETED = 'User Deleted'
Add the decorator to your controller to set response message metadata
#Patch('/:userId')
#HttpCode(201)
#ResponseMessage(USER_UPDATED)
public async updateUser(
#Param('userId') userId: string,
#Body() userUpdate: UpdateUserDto
): Promise<any> {
const result = await this.usersService.update(userId, userUpdate);
return result;
}
Update your interceptor to read the response message from the metadata set on the controller and add it in the response
import { Reflector } from '#nestjs/core'
#Injectable()
export class TransformationInterceptor<T>
implements NestInterceptor<T, Response<T>>
{
constructor(private reflector: Reflector) {}
intercept(
context: ExecutionContext,
next: CallHandler
): Observable<Response<T>> {
const responseMessage = this.reflector.get<string>(
ResponseMessageKey,
context.getHandler()
) ?? ''
return next.handle().pipe(
map((data) => ({
data,
statusCode: context.switchToHttp().getResponse().statusCode,
message: responseMessage
}))
)
}
}
You could extend this approach to set a list of strings/objects as possible responses (metadata), and based on response code in interceptor, send a particular message as response.message
Related
I want to set login, logout callback url.
So, I set the callback url like this.
//signIn
const signInResult = await signIn("credentials", {
message,
signature,
redirect: false,
callbackUrl: `${env.nextauth_url}`,
});
//signOut
signOut({ callbackUrl: `${env.nextauth_url}`, redirect: false });
But, When I log in, I look at the network tab.
api/auth/providers, api/auth/callback/credentials? reply with
callbackUrl(url) localhost:3000
It's api/auth/callback/credentials? reply.
It's api/auth/providers reply
and api/auth/session reply empty object.
When I run on http://localhost:3000, everything was perfect.
But, After deploy, the login is not working properly.
How can I fix the error?
I added [...next-auth] code.
import CredentialsProvider from "next-auth/providers/credentials";
import NextAuth from "next-auth";
import Moralis from "moralis";
import env from "env.json";
export default NextAuth({
providers: [
CredentialsProvider({
name: "MoralisAuth",
credentials: {
message: {
label: "Message",
type: "text",
placeholder: "0x0",
},
signature: {
label: "Signature",
type: "text",
placeholder: "0x0",
},
},
async authorize(credentials: any): Promise<any> {
try {
const { message, signature } = credentials;
await Moralis.start({
apiKey: env.moralis_api_key,
});
const { address, profileId } = (
await Moralis.Auth.verify({ message, signature, network: "evm" })
).raw;
if (address && profileId) {
const user = { address, profileId, signature };
if (user) {
return user;
}
}
} catch (error) {
console.error(error);
return null;
}
},
}),
],
pages: {
signIn: "/",
signOut: "/",
},
session: {
maxAge: 3 * 24 * 60 * 60,
},
callbacks: {
async jwt({ token, user }) {
user && (token.user = user);
return token;
},
async session({ session, token }: any) {
session.user = token.user;
return session;
},
async redirect({ url, baseUrl }) {
// Allows relative callback URLs
if (url.startsWith("/")) return `${baseUrl}${url}`;
// Allows callback URLs on the same origin
else if (new URL(url).origin === baseUrl) return url;
return baseUrl;
},
},
secret: env.nextauth_secret,
});
I'm new on graphql and i tried to implement a basic API with express.
But, when i tried to request post author these return null.
My "Post" type :
const postType = `
type Post {
id: ID
title: String
content: String
author: User
}
`;
module.exports = postType
My "User" type :
const userType = `
type User {
id: ID
name: String
age: Int
}
`;
module.exports = userType
My Graphql API schema :
const schema = buildSchema(`
${userType}
${postType}
type Query {
users: [User!]!
posts: [Post!]!
user(id: Int): User!
post(id: Int): Post!
}
type Mutation {
createUser(user: createUserInput): User
}
schema {
query: Query
mutation: Mutation
}
`);
module.exports = schema
My "Post" resolver and type implementation with ES6 class :
const { getUser } = require('../actions/index').user
const { getPost, getPosts } = require('../actions/index').post
class Post {
constructor(post) {
Object.assign(this, post)
}
async author() {
const data = await getUser(this.post.author)
return data
}
}
const postResolver = {
posts: async () => {
const data = await getPosts()
return data.map(post => new Post(post))
},
post: async ({ id }) => new Post(await getPost(id))
}
module.exports = postResolver
My "User" resolver and type implementation with ES6 class :
const { getUsers, getUser, createUser } = require('../actions/index').user
class User {
constructor(user) {
Object.assign(this, user)
}
}
const userResolver = {
users: async () => {
const data = await getUsers()
return data.map(user => new User(user))
},
user: async ({ id }) => new User(await getUser(id)),
}
module.exports = userResolver
The client query and response :
query {
post(id: 1) {
id
title
author {
id
}
}
}
// response
{
"data": {
"post": {
"id": "1",
"title": "Post 1",
"author": {
"id": null
}
}
}
}
Someone can help me please ? Thanks !
I am trying to split up my schema of GraphQL API into separate ones.
Having familiarized with a huge number of them I decided on this one below using "extend"
( I also wanted to use .graphql file extension for them but as I've got there is only one way to do that - it is with the use of webpack. I am not really good at it so I tried to make it work at first with .js files)
BUT: I cannot cope with this simple task because I either have TypeError: Cannot read property 'kind' of undefined or Invalid schema passed, or something else pops up...
What am I doing wrong here and what is the best practice/approach to split up and stitch the Schema?
Thanks in advance!
server.js
import express from "express";
import { ApolloServer, gql } from "apollo-server-express";
import { makeExecutableSchema } from 'graphql-tools';
import * as mongoClient from "./config";
import * as _ from 'lodash';
import { UserSchema, UserResolvers } from "./graphql.partials/user.api";
const port = process.env.PORT || 8080;
const RootSchema = gql`
type Query {
_empty: String
}
type Mutation {
_empty: String
}
`
const RootResolvers = {
};
const app = express();
const schema = makeExecutableSchema({
typeDefs: [RootSchema, UserSchema],
resolvers: _.merge(RootResolvers, UserResolvers)
});
const apolloServer = new ApolloServer({ schema });
apolloServer.applyMiddleware({ app });
app.listen({ port }, () => {
console.log(
`Server ready at http://localhost:${port}${apolloServer.graphqlPath}`
);
});
user schema
const { gql } = require("apollo-server-express");
import User from '../models/User';
export const typeDefs = gql`
extend type Query {
users: [User]
user(id: String): User
}
extend type Mutation {
addUser(email: String, password: String): User
deleteUser(id: String!): User
updateUser(user: UserInput): User
}
type User {
id: ID!
firstName: String
lastName: String
email: String!
password: String!
confirmed: Boolean
role: String
}
input UserInput {
id: ID!
firstName: String
lastName: String
email: String
password: String
confirmed: Boolean
role: String
}
`
export const UserResolvers = {
Query: {
users: async (obj, args, context, info) => {
try {
const res = await User.find();
return res;
} catch (err) {
return err.message;
}
},
user: async (obj, args, context, info) => {
try {
const res = User.findById(args['id']);
return res;
} catch (e) {
return e.message;
}
}
},
Mutation: {
addUser: async (obj, args, context, info) => {
try {
const res = await User.create(args);
return res;
} catch (err) {
return err.message;
}
},
deleteUser: async (obj, args, context, info) => {
try {
return User.findByIdAndDelete(args['id']);
} catch (e) {
return e.message;
}
},
updateUser: async (obj, args, context, info) => {
try {
return User.findByIdAndUpdate(args.user.id, args.user)
} catch (e) {
return e.message;
}
}
}
}
There is no export named UserSchema in your file. You have two named exports -- UserResolvers and typeDefs. As a result, when you attempt to import UserSchema, its value is undefined.
I'm getting data from the backend and want to store it in the state.
The server sends some user data (id, mail, name,.. ) as an object. This object I want to store as an attribute of the state object.
Here is the code,
starting with auth.js (action-file):
//loadUser(), which gets called via useEffect in App.js:
export const loadUser = () => async dispatch => {
try {
const token = await AsyncStorage.getItem('token')
await setToken(token)
const res = await axios.get(`${SERVER_IP}/api/auth`)
console.log('res.data', res.data) // see result of clog below
dispatch({
type: USER_LOADED,
payload: res.data
})
} catch (err) {
dispatch({
type: AUTH_ERROR
})
}
}
this is the result of console.log('res.data', res.data), which gets passed on to the reducer. This is also what I want:
Object {
"__v": 0,
"_id": "5d2b422322cdf413d4246566",
"avatar": "//www.gravatar.com/avatar/e14a77efcd408a95332f403e0db40b95?s=200&r=pg&d=mm",
"date": "2019-07-14T14:54:27.265Z",
"email": "awnwen#asdw.de",
"name": "awena",
}
This is what the reducer looks like:
// reducer:
import {
USER_LOADED,
AUTH_ERROR,
} from '../actions/types'
import {getToken, setToken, removeToken} from '../actions/auth'
const initialState = {
token: getToken(),
isAuthenticated: null,
loading: true,
user: null
}
export default async function (state = initialState, action) {
const { type, payload } = action
switch (type) {
case USER_LOADED:
return {
...state,
user: payload,
isAuthenticated: true,
loading: false
}
case AUTH_ERROR:
await removeToken()
return {
...state,
isAuthenticated: false,
token: null,
user: null,
loading: false
}
default:
return state
}
}
In case they are needed: token functions from auth.js actions-file (first file posted)
export const getToken = async () => {
try {
return await AsyncStorage.getItem('token')
} catch (error) {
console.log("Tokenerror: getToken() -" + error);
return null
}
}
export const setToken = async (token) => {
try {
await AsyncStorage.setItem('token', token);
setAuthToken(token)
} catch (error) {
console.log("Tokenerror: setToken() - " + error);
return null
}
}
export const removeToken = async () => {
try {
return await AsyncStorage.removeItem('token')
} catch (error) {
console.log("Tokenerror: removeToken() - " + error);
return null
}
}
I expect the auth state to be like:
{
auth: {
token: 'randomstringofnumbersandletters',
isAuthenticated: true,
loading: false,
user: {
name: 'john',
email: 'jdoe#gmail.com',
.....
}
}
}
This is what RND shows in auth state:
{
auth: {
_40: 0,
_65: 0,
_55: null,
_72: null
}
}
This is my first issue I am posting on stackoverflow, it says I should add more text because the text to code ratio is too bad.. Please feel free to ask questions if you need more information to help me resloving this issue.
Many thanks in advance, guys ;)
EDIT1:
As requested I console logged the payload in the reducer after the USER_LOADED case.
switch (type) {
case USER_LOADED:
console.log('payload: ', payload)
return {
...state,
user: payload,
isAuthenticated: true,
loading: false
}
It is returning the same object:
payload: Object {
"__v": 0,
"_id": "5d2b422322cdf413d4246566",
"avatar": "//www.gravatar.com/avatar/e14a77efcd408a95332f403e0db40b95?s=200&r=pg&d=mm",
"date": "2019-07-14T14:54:27.265Z",
"email": "awnwen#asdw.de",
"name": "awena",
}
The problem could be solved by removing "async" from
export default async function (state = initialState, action) {...}
in the reducer.
Now I get the user in state.
But the token in the auth state is not a string but an object/Promise.
This is what I get from logging the token property in the auth state object:
state.token Promise {
"_40": 0,
"_55": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNWQyYjc2MzRmN2U1MjcwOGUwNjNmZThmIn0sImlhdCI6MTU2MzEyOTM5NiwiZXhwIjoxODYzMTI5Mzk2fQ.YQayK3HBXIFl6aFSfkojTF8WrZkxXCGMf36Z0rXvRjY",
"_65": 1,
"_72": null,
}
I am not very experienced with promises. Is there a way to only store the value of the token as a string in my state?
I could build and ugly workaround and extract the value of the key with _55.
But on the one hand I don't understand what those keys in the promise are AND I could imagine the keys to change from one client to another.
If have a form with a text field and file upload option. The file is called start.vue(say). From that a module.js(say) file is called. Using that service.js(say) is called. That service.js calls the api in moveController.java(say). I am getting error as: Current request is not a multipart request
Tried to find the syntax so that multipart header is retained till api is called. module.js is getting the appropriate file value. But no idea about service.js
start.vue is:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
export default {
name: "BatchMove",
computed: {
loading() {
return this.$store.state.loader.loading;
},
msgs() {
return this.$store.state.moveBatchHistory.msg;
}
},
components: {},
data() {
return {
file: '',
emailId: ''
};
},
methods: {
submitFile() {
console.log("logging email... " + this.emailId);
const { emailId, file } = this;
this.$store.dispatch("moveBatchHistory/ans", { file, emailId });
},
handleFileUpload(){
this.file = this.$refs.file.files[0];
}
}
}
</script>
module.js is:
import {
moveBatchHistoryService
} from '../_services';
export const moveBatchHistory = {
namespaced: true,
state: {
msg: null
},
actions: {
ans({commit}, {file, emailId}) {
moveBatchHistoryService.getSomething(file, emailId).then(
response => { commit("ans", response); }
);
}
},
mutations: {
ans(state, response) {
state.msg = response
}
}
}
service.js is:
import config from '../config';
import {authHeader} from '../_helpers';
import axios from 'axios';
import {store} from '../_store';
export const moveBatchHistoryService = {
getSomething
};
function getSomething(file, emailId) {
var headers = authHeader();
const requestOptions = {
method: 'POST',
headers: headers,
params: {
"file": file,
"Email": emailId
}
};
return axios(`${config.apiUrl}/api/moveBatch`, requestOptions)
.then(response => {
store.dispatch("alert/errorTime", "We are here!");
return response.data;
}).catch(() => {
store.dispatch("alert/errorTime", "Unable to process your request this time. Please try again latter.");
});
}
'''
moveController.java is:
#RequestMapping(value = "/moveBatch", method = RequestMethod.POST)
public void singleFileUpload(#RequestParam("file") MultipartFile file, String Email) throws Exception {}
current request is not multipart request in moveController.java
I have done the following way.
import config from '../config';
import {authHeader} from '../_helpers';
import axios from 'axios';
import {store} from '../_store';
export const moveBatchHistoryService = {
getSomething
};
function getSomething(file, emailId) {
let formData = new FormData();
formData.append('file', file);
formData.append('Email',emailId);
return axios.post(`${config.apiUrl}/api/moveBatch`, formData, { headers: { 'Content-Type': 'multipart/form-data' } })
.then(response => {
store.dispatch("alert/successTime", "Successfully completed the request!");
return response.data;
}).catch(() => {
store.dispatch("alert/errorTime", "Unable to process your request this time. Please try again latter.");
});
}
I am not very good at vue.js but for posting the data you could refer this post
As for as backed is concern you have not mentioned whether the email is #RequestParam, #RequestBody. Use this below for that
#RequestMapping(value = "/moveBatch", method = RequestMethod.POST)
public ResponseEntity singleFileUpload(#RequestPart("Email") String Email,
#RequestPart("file") MultipartFile dataFile) throws IOException {
System.out.println(String.format("Data - %s", email));
return ResponseEntity.ok().build();
}
So when you upload File along with any body or Param the content-type will be multipart/form-data so make sure in client side you add the type
so why #RequestPart, if you see the source code they have said the below
Annotation that can be used to associate the part of a "multipart/form-data" request with a method argument.
Look into it.