Graphql can't retrieve sub type - express

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 !

Related

(redis-om & fastify) this.writeEntity is not a function

I'm learning to use the Redis for my backend database and I would like to try using redis-om for fastify not sure whether they are compatible or not, but I got error.
I use service of app.redislabs.com
I don't know what I just messed up? And how can I fix the problem?
server.js
const { createCar, createIndex } = require("./redis");
app.post("/add", async (req, res) => {
await createIndex();
const { make, model, image, description } = req.body;
const data = { make, model, image, description };
await createCar(data);
res.code(200).send('ok');
});
const PORT = 5000;
app.listen(PORT, function (err) {
if (err) {
app.log.error(err);
process.exit(1);
}
});
redis.js
const { Client, Entity, Schema, Repository } = require("redis-om");
const client = new Client();
const connect = async () => {
if (!client.isOpen()) {
await client.open("redis://default:password#localhost:6379");
} else {
console.log("CONNECTED");
}
};
class Car extends Entity {}
let schema = new Schema(
Car,
{
make: { type: "string" },
model: { type: "string" },
image: { type: "string" },
description: { type: "string" },
},
{ dataStructure: "JSON" }
);
const createCar = async (data) => {
await connect();
const repository = new Repository(schema, client);
const car = repository.createEntity(data);
const id = await repository.save(car);
return id;
};
const createIndex = async () => {
await connect();
const repository = new Repository(schema, client);
await repository.createIndex();
};
module.exports = {
createCar,
createIndex,
};
My JSON Body
You cannot call new on Repository. This is a breaking change I introduced in version 0.2.0 of Redis OM. There are a couple of others that are documented in the CHANGELOG.
Call const repository = client.fetchRepository(schema) instead, as shown here. Unfortunately, there are some videos and blogs that have the older syntax and so this crops up from time to time.
Thanks for using my library!

Can not run multiple tests in a file

I'm building a GraphQL API and I want to test some resolvers and the database with jest.
Here is my helper file, where I set up the context and the Prisma Client for testing.
import { PrismaClient } from "#prisma/client";
import { ServerInfo } from "apollo-server";
import { execSync } from "child_process";
import getPort, { makeRange } from "get-port";
import { GraphQLClient } from "graphql-request";
import { nanoid } from "nanoid";
import { join } from "path";
import { Client } from "pg";
import { server } from "../api/server";
type TestContext = {
client: GraphQLClient;
db: PrismaClient;
};
export function createTestContext(): TestContext {
let ctx = {} as TestContext;
const graphqlCtx = graphqlTestContext();
const prismaCtx = prismaTestContext();
beforeEach(async () => {
const client = await graphqlCtx.before();
const db = await prismaCtx.before();
Object.assign(ctx, {
client,
db,
});
});
afterEach(async () => {
await graphqlCtx.after();
await prismaCtx.after();
});
return ctx;
}
function graphqlTestContext() {
let serverInstance: ServerInfo | null = null;
return {
async before() {
const port = await getPort({ port: makeRange(4000, 6000) });
serverInstance = await server.listen({ port });
return new GraphQLClient(`http://localhost:${port}`);
},
async after() {
serverInstance?.server.close();
},
};
}
function prismaTestContext() {
const prismaBinary = join(__dirname, "..", "node_modules", ".bin", "prisma");
let schema = "";
let databaseUrl = "";
let prismaClient: null | PrismaClient = null;
return {
async before() {
schema = `test_${nanoid()}`;
databaseUrl = `postgresql://user:123#localhost:5432/testing?schema=${schema}`;
process.env.DATABASE_URL = databaseUrl;
execSync(`${prismaBinary} migrate up --create-db --experimental`, {
env: {
...process.env,
DATABASE_URL: databaseUrl,
},
});
prismaClient = new PrismaClient();
return prismaClient;
},
async after() {
const client = new Client({
connectionString: databaseUrl,
});
await client.connect();
await client.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
await client.end();
await prismaClient?.$disconnect();
},
};
}
My test file looks like this:
import { createTestContext } from "./__helpers";
const ctx = createTestContext();
it("register user", async () => {
const testUser = {
username: "Test",
email: "test#test.com",
password: "password",
};
const registerResult = await ctx.client.request(
`
mutation registerNewUser($username: String!, $email: String!, $password: String!) {
register(username: $username, email: $email, password: $password) {
user {
user_id
username
email
}
}
}
`,
{
username: testUser.username,
email: testUser.email,
password: testUser.password,
}
);
const resultUsername = registerResult.register.user.username;
const resultEmail = registerResult.register.user.email;
const resultUserID = registerResult.register.user.user_id;
expect(resultUsername).toBe(testUser.username);
expect(resultEmail).toBe(testUser.email);
expect(resultUserID).not.toBeNull;
const users = await ctx.db.user.findMany();
const savedUser = users[0];
expect(savedUser.username).toBe(testUser.username);
expect(savedUser.email).toBe(testUser.email);
expect(savedUser.user_id).toBe(resultUserID);
expect(savedUser.first_name).toBeNull;
expect(savedUser.last_name).toBeNull;
expect(savedUser.role).toBe("USER");
expect(savedUser.password).not.toBe(testUser.password);
});
it("all events", async () => {
const eventsResult = await ctx.client.request(
`
query {
allEvents {
event_id
title
description
}
}
`
);
expect(eventsResult.allEvents.length).toBe(0)
});
When I just run one file with one test in it, everything works. But when I run multiple tests in one file, the first one runs normal, but the ones after not. I receive this error:
The table `test_LjrcmbMjI4vLaDYM9-lvw.Event` does not exist in the current database.: {"response":{"errors":[{"message":"\nInvalid `prisma.event.findMany()` invocation:\n\n\n The table `test_LjrcmbMjI4vLaDYM9-lvw.Event` does not exist in the current database.","locations":[{"line":3,"column":7}],"path":["allEvents"],"extensions":{"code":"INTERNAL_SERVER_ERROR","exception":{"code":"P2021","clientVersion":"2.11.0","meta":{"table":"test_LjrcmbMjI4vLaDYM9-lvw.Event"}}}}],"data":null,"status":200},"request":{"query":"\n query {\n allEvents {\n event_id\n title\n description\n }\n }\n "}}
Also when I run two tests in separated files, on every second test run I get this error:
listen EADDRINUSE: address already in use :::4200
I did the nexus tutorial (Step 4, and 5), where they explained how to test, but somehow it doesn't work. So please help me.
https://nexusjs.org/docs/getting-started/tutorial
I have created a repo with parallel tests for the same here. The test environment setup is in the prisma folder and a similar helper is created in the tests folder.

Deny PUT request if condition isn't met

I'm having issues denying a PUT request if the logged-in user tries to update their email address. I want to make sure the only the authorized user can only update their own email. It doesn't seem to like my res.end()or return; How can I write my code to meet this condition before updating?
app.js
app.patch('/:id', (req, res) => {
if(req.body.oldEmail){
let user = req.body.id;
if (user.email !== req.body.oldEmail) {
res.sendStatus(401);
} else {
User.update(
{email: req.body.oldEmail},
{email: req.body.newEmail}
).then(user => {
console.log(user);
res.json(user);
}).catch(err => console.log(err));
}
}
auth.service.ts
import { Injectable } from "#angular/core";
import { HttpClient } from "#angular/common/http";
import { Router } from "#angular/router";
import { Subject } from "rxjs";
import { AuthData } from "./auth-data.model";
import { AuthDataLogin } from "./auth-data-login.model";
import { LoginService } from "./login/login.service";
#Injectable({ providedIn: "root" })
export class AuthService {
private isAuthenticated = false;
private token: string;
private tokenTimer: any;
private userName: string;
private authStatusListener = new Subject<boolean>();
private userId: string;
constructor(
private http: HttpClient,
private router: Router,
private loginService: LoginService
) {}
getToken() {
return this.token;
}
getIsAuth() {
return this.isAuthenticated;
}
getUserId() {
return this.userId;
}
getAuthStatusListener() {
return this.authStatusListener.asObservable();
}
createUser(
email: string,
password: string,
instagramName: string,
over21: boolean,
role: string
) {
const authData: AuthData = {
email: email,
password: password,
instagramName: instagramName,
over21: over21,
role: role,
fullName: "Not Added Yet",
address1: "none",
address2: "none",
city: "none",
state: "none",
zip: "none"
};
this.http
.post("http://localhost:3000/api/user/signup", authData)
.subscribe(response => {
console.log(response);
});
}
login(email: string, password: string) {
const authData: AuthDataLogin = { email: email, password: password };
this.http
.post<{
token: string;
expiresIn: number;
userId: string;
instagramName: string;
}>("http://localhost:3000/api/user/login", authData)
.subscribe(response => {
const token = response.token;
console.log("Response");
console.log(response);
// this.userName = response;
// console.log(this.userName);
this.userName = response.instagramName;
console.log(this.userName);
this.token = token;
if (token) {
const expiresInDuration = response.expiresIn;
this.setAuthTimer(expiresInDuration);
this.isAuthenticated = true;
this.userId = response.userId;
this.userName = response.instagramName;
this.authStatusListener.next(true);
const now = new Date();
const expirationDate = new Date(
now.getTime() + expiresInDuration * 1000
);
console.log(expirationDate);
this.saveAuthData(token, expirationDate, this.userId);
this.router.navigate(["/"]);
let key = "UserID";
}
});
}
autoAuthUser() {
const authInformation = this.getAuthData();
if (!authInformation) {
return;
}
const now = new Date();
const expiresIn = authInformation.expirationDate.getTime() - now.getTime();
if (expiresIn > 0) {
this.token = authInformation.token;
this.isAuthenticated = true;
this.userId = authInformation.userId;
this.setAuthTimer(expiresIn / 1000);
this.authStatusListener.next(true);
}
}
logout() {
this.token = null;
this.isAuthenticated = false;
this.authStatusListener.next(false);
clearTimeout(this.tokenTimer);
this.clearAuthData();
this.userId = null;
//location.reload();
this.router.navigate(["/login"]);
}
private setAuthTimer(duration: number) {
//console.log("Setting timer: " + duration);
this.tokenTimer = setTimeout(() => {
this.logout();
}, duration * 1000);
}
private saveAuthData(token: string, expirationDate: Date, userId: string) {
localStorage.setItem("token", token);
localStorage.setItem("expiration", expirationDate.toISOString());
localStorage.setItem("userId: ", userId);
localStorage.setItem("username", this.userName);
}
private clearAuthData() {
localStorage.removeItem("token");
localStorage.removeItem("expiration");
localStorage.removeItem("userId");
localStorage.removeItem("username");
}
private getAuthData() {
const token = localStorage.getItem("token");
const expirationDate = localStorage.getItem("expiration");
const userId = localStorage.getItem("userId: ");
if (!token || !expirationDate) {
return;
}
return {
token: token,
expirationDate: new Date(expirationDate),
userId: userId
};
}
}
There are a couple problems with your code:
It looks like you are updating the email address before doing the check to see if the users email is the same.
You should use res.sendStatus(401) instead of res.end()
Try changing your code to something like this and see if that does what you want:
app.put('/email/:id', (req, res) => {
let user = // get the user first to check the email.
if (user.email !== req.body.oldEmail) {
res.sendStatus(401);
} else {
User.update(
{email: req.body.oldEmail},
{email: req.body.newEmail}
).then(user => {
console.log(user);
res.json(user);
}).catch(err => console.log(err));
}
});

Is it possible create a moleculer service with many Validator instances?

I would like to have more than one Validator instance on my service to handle different languages. Is there any way to implement that?
Something like that:
{
en: new Validator({ messages: { ... }}),
de: new Validator({ messages: { ... }})
// ...
}
It is not available. You should create a custom multi-validators. Here is a quick example:
"use strict";
const _ = require("lodash");
const { ServiceBroker } = require("moleculer");
const BaseValidator = require("moleculer").Validators.Base;
const Validator = require("fastest-validator");
const DefaultMessages = require("fastest-validator/lib/messages");
const { ValidationError } = require("moleculer").Errors;
// --- I18N VALIDATOR CLASS ---
class I18NValidator extends BaseValidator {
constructor(messages) {
super();
0;
this.validators = {};
Object.keys(messages).forEach(lang => {
this.validators[lang] = new Validator();
this.validators[lang].messages = Object.assign({}, DefaultMessages, messages[lang]);
});
}
compile(schema) {
this.checks = {};
Object.keys(this.validators).forEach(lang => {
this.checks[lang] = this.validators[lang].compile(schema);
});
return this.checks;
}
middleware() {
return function I18NValidator(handler, action) {
// Wrap a param validator
if (action.params && typeof action.params === "object") {
const checks = this.compile(action.params);
return function validateContextParams(ctx) {
const check = checks[ctx.meta.lang] || checks["en"];
const res = check(ctx.params);
if (res === true)
return handler(ctx);
else
return Promise.reject(new ValidationError("Parameters validation error!", null, res));
};
}
return handler;
}.bind(this);
}
}
let broker = new ServiceBroker({
logger: true,
validation: true,
validator: new I18NValidator({
"en": {
"string": "The '{field}' field must be a string!"
},
"hu": {
"string": "A '{field}' mezőnek szövegnek kell lennie!"
}
})
});
// --- TEST BROKER ---
broker.createService({
name: "greeter",
actions: {
hello: {
params: {
name: { type: "string", min: 4 }
},
handler(ctx) {
return `Hello ${ctx.params.name}`;
}
}
}
});
broker.start()
// No meta lang
.then(() => broker.call("greeter.hello", { name: 100 }).then(res => broker.logger.info(res)))
.catch(err => broker.logger.error(err.message, err.data))
// "hu" lang
.then(() => broker.call("greeter.hello", { name: 100 }, { meta: { lang: "hu" }}).then(res => broker.logger.info(res)))
.catch(err => broker.logger.error(err.message, err.data))
// "en" lang
.then(() => broker.call("greeter.hello", { name: 100 }, { meta: { lang: "en" }}).then(res => broker.logger.info(res)))
.catch(err => broker.logger.error(err.message, err.data));
It reads the lang from the ctx.meta.lang but you can change it for your case.

Vuex - Normalizr doesn't work as expected

I am creating a simple chat app. I have three entities: rooms, messages and users.
I have a fake API that returns a response like this:
[{
id: 1,
name: 'room1',
avatar: 'some img url',
messages: [
{
id: 1,
text: 'some text',
user: {
id: 1,
username: 'Peter Peterson',
avatar: 'some img url'
}
]
}]
And my action looks like this:
getAllRooms({ commit }) {
commit(GET_ALL_ROOMS_REQUEST);
return FakeApi.getAllRooms()
.then(
rooms => {
const { entities } = normalize(rooms, room);
console.log(entities);
commit(GET_ALL_ROOMS_SUCCESS, {
rooms: entities.rooms, byId: rooms.map(room => room.id)
});
commit(GET_ALL_MESSAGES_SUCCESS, { messages: entities.messages });
commit(GET_ALL_USERS_SUCCESS, { users: entities.users });
},
err => commit(GET_ALL_ROOMS_ERROR)
)
}
And my mutations look like this:
[GET_ALL_ROOMS_REQUEST](state) {
state.loading = true;
},
[GET_ALL_ROOMS_SUCCESS](state, payload) {
state.rooms = payload.rooms;
state.byId = payload.byId;
state.loading = false;
},
[GET_ALL_ROOMS_ERROR]() {
state.error = true;
state.loading = false;
}
And my component calls the action like this:
{
mounted() {
this.getAllRooms();
}
}
These are my schema definitions:
const user = new schema.Entity('users');
const message = new schema.Entity('messages', {
user: user
});
const room = new schema.Entity('rooms', {
messages: [message]
})
when i check the response in then method after FakeApi.getAllRooms() every object is wrapped in some weird Observer, and I pass it like that to normalize and normalize returns some weird response.
What am I doing wrong?
The problem wasn't with vuejs, it was with the way I made the normalizr schemas. Because my response is an array at the root I should have had a new rooms array schema, like so:
const user = new schema.Entity('users');
const message = new schema.Entity('messages', {
user: user
});
const room = new schema.Entity('rooms', {
messages: [message]
});
const roomsSchema = [room];
And then use it like this: normalize(rooms, roomsSchema)