How to implement shared session store with fastify - session

I am using apollo server 4 + fastify with #mgcrea/fastify-session
as my session store and can't get data to be shared between multiple requests.
My cookie configuration looks like this.
app.register(fastifySession, {
cookieName: "qid",
saveUninitialized: true,
store: new RedisStore({
client: new Redis({ enableAutoPipelining: true }),
}),
secret: "cats",
cookie: {
maxAge: 31536e7,
httpOnly: true,
sameSite: "lax",
secure: "auto",
},
});
My resolver looks like this.
#Query(() => Int, { nullable: true })
test(#Ctx() { req }: MyCtx): number {
req.session.set("5", 2);
return req.session.get("5") as number;
}
The code directly above returns 2 as expected. But when I use multiple resolvers it doesn't share the data as I would like it.
#Query(() => Int, { nullable: true })
test(#Ctx() { req }: MyCtx): number {
req.session.set("5", 2);
return req.session.get("5") as number;
}
#Query(() => Int, { nullable: true })
test2(#Ctx() { req }: MyCtx): number {
return req.session.get("5") as number;
}
Even when I execute test first then test2. test2 always returns null.
How can I remedy this?
I tried to use the req.session.data object to store data instead but that didn't solve the issue.

Related

How to use RTK Query in createSlice?

I want to process the data that I get from the request in the slice.
Because not all slices are async (but work with the same data), transformResponse is not suitable.
Is there anything you can suggest?
My code example:
Some RTK Query
export const currencyApi = createApi({
reducerPath: 'currencyApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://api.apilayer.com/exchangerates_data' }),
endpoints: (build) => ({
fetchCurrencyRates: build.query<IApiResponse, string>({
query: (currency) => ({
url: '/latest',
params: {
base: currency
},
headers: {
apikey: *SomeApiKey*
}
})
})
})
})
Slice where I want to use data from RTK requests
const initialState: ICurrencyState = {
currencyRates: {},
availableCurrencyOptions: [],
fromCurrency: '',
toCurrency: '',
exchangeRate: 0,
error: null
}
export const currencySlice = createSlice({
name: 'currency',
initialState,
reducers: {
//
}
})
Use Hooks in Components
You can send the received data to the slice via useEffect. Something like this:
const { data } = useFetchCurrencyRatesQuery();
useEffect(() => {
if (data !== undefined) {
dispatch(...)
}
}, [data])

Setup & configure NestJS 9 (TypeORM 0.3+) to use custom repositories

I am currently attempting to update my project to the latest version of NestJS and TypeORM and I am having a great deal of difficulty wrapping my head around how to setup the project so that I can use custom repositories in a similar way that it was before.
The biggest problem is that #EntityRepository and .getCustomRepository have been deprecated. I have been pouring over documentation and I can't seem to figure out the new way of doing things.
From the TypeORM docs, it says to do something like this:
export const UserRepository = dataSource.getRepository(User).extend({
findByName(firstName: string, lastName: string) {
return this.createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName })
.andWhere("user.lastName = :lastName", { lastName })
.getMany()
},
})
ok... I get that... use the dataSource to create the repo and extend it with the additional functions. Seem's easy enough, right?
WRONG!
You can't create the repo until the dataSource object is created. In straight up TypeORM you can create your new DataSource but in nest, it's an async bootstrap... which means when you import your repo/service files the data source isn't yet there.
grrr.
So, here is my code (simplified).
Basically, it's a small app with users. The users have a role. So you can get the user by name, or update the user name so long as it has a role. If the role is null, then you can't do it. (the create method is not included, assume it's there)
app.config.ts
// database values and such are loaded from an env file. (not included)
export const applicationModuleConfig: ApplicationModuleConfig = {
typeOrmConfig: {
type: "postgres",
host,
username,
database,
port,
synchronize: false,
cache: true,
entities: [resolve(__dirname, "./**/*.entity.ts"), resolve(__dirname, "./**/*.entity.js")],
migrations: [resolve(__dirname, "./migrations/**/*{.ts,.js}")],
migrationsTableName: "migrations",
migrationsRun: true,
password,
}
}
users.entity.ts (trimmed, assume there are more fields/columns)
#Entity()
#ObjectType("Users")
export class Users {
#Field(() => Int)
#PrimaryGeneratedColumn()
id: number;
#Field()
#Column({ type: "varchar", nullable: false, default: "" })
name: string;
#Field()
#Column({ type: "varchar", nullable: false, default: "" })
role: string;
}
users.repository.ts
export const UsersRepository = dataSource.getRepository(Users).extend({
findByName(name: string) {
return this.createQueryBuilder("users")
.where("users.name = :name", { name })
.getOne()
},
async verifyRole(id: number) {
const user = await this.createQueryBuilder("users")
.where("users.id = :id", { id })
.getOne()
return user?.role !== null;
},
})
users.service.ts
#Injectable()
export class UsersService {
constructor(private readOnly usersRepo: UsersRepository) {}
findByName(name: string) {
return this.usersRepo.findByName(name);
}
verifyRole(id: number) {
return this.usersRepo.verifyRole(id);
}
}
users.resolver.ts
#Resolver(() => User)
#UseGuards(GraphqlAuthGuard, PermissionsGuard, ScopeGuard)
export class UserResolver {
constructor(private readonly usersService: UsersService) {}
#Query(() => User, { name: "user" })
async getUser(
#Args({
name: "name",
type: () => String,
})
name: string,
#CurrentUser() currentUser: AuthorizedUser,
): Promise<Users> {
return this.usersService.findByName(name);
}
// assume EditUsersInput is a object matching the entity fields.
#Mutation(() => Users)
#VerifyUser<{ input: EditUsersInput }>(({ input }) => {
// >>> this doesn't work <<<*
return UsersRepository.verifyRole(input.id);
})
async editUser(
#Args(
{
name: "input",
type: () => EditUsersInput,
},
new ValidationPipe(),
)
input: EditUsersInput,
): Promise<Users> {
await UsersRepository.editUser(input); // assume the editUser method exists in the service and repo.
return UsersRepository.findByUserId(input.id); // assume the findByUserId method exists in the service and repo.
}
app.module.ts (I will admit I am not sure what I am supposed to be doing in this file)
#Global()
#Module({
imports: [TypeOrmModule.forFeature([...entitiesWithoutAModuleHere])],
providers: [UsersService, UsersRepository],
exports: [UsersService, UsersRepository],
})
export class ApplicationModule {
static forRoot(config: ApplicationModuleConfig): DynamicModule {
const {
graphql,
typeOrmConfig,
numConcurrentReportJobs,
redis: { tls: redisTls, ...redisRest },
} = config;
////
// a bunch of redis code goes in here...
////
return {
module: ApplicationModule,
imports: [
// omitted unrelated imports, such as bullQueue and stuff like that.
TypeOrmModule.forRoot({
...typeOrmConfig,
}),
GraphQLModule.forRoot(graphql),
ConfigurationModule.forRoot(config), // I am not sure if this file is important or not, but I am omitting it for now.
],
};
}
constructor(public dataSource: DataSource) {
// AH HA! The dataSource has been initialized by nest!
// but, the repository errors have already occurred and it never got this far.
// I don't know what to do here.
}
}
main.ts
import blah from "blah";
import { applicationModuleConfig } from "./app.config";
import everythingElse from "otherStuff";
import { ApplicationModule } from "./app.module"; // <-- KaBOOM!
async function bootstrap() {
initializeTransactionalContext();
patchTypeORMRepositoryWithBaseRepository();
const ApplicationModule = require("./app.module");
const app = await NestFactory.create(ApplicationModule.forRoot(applicationModuleConfig), { bodyParser: false });
app.use(
session({
resave: false, //As per https://github.com/expressjs/session#resave
saveUninitialized: false,
store: new (require("connect-pg-simple")(session))({
createTableIfMissing: true,
}),
secret: process.env.APP_SESSION_SECRET, cookie: { secure: secureCookies },
}),
);
await app.listen(3000);
}
bootstrap().then(() => null);
I know I am doing it wrong, but I can't figure out how to do it right. Any thoughts, comments, suggestions, examples, tutorials, or explanations of the right way?
Thanks!

pull Promise result outside of a nested map react js

I'm working at a React app and I need to loop inside an array containing objects with this structure:
const servers = [
{
name: "Server A",
url: "https://server-one.com/version",
accessToken: "yJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLC",
subVersions: [
{
name: "Subversion A1",
ip: "https://10.4.20/version",
accessToken: "yJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLC"
},
{
name: "Subversion A2",
ip: "https://10.4.20/v1/version",
accessToken: "yJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLCyJ0eXAiOiJKV1QiLC"
}
]
},
// ... more servers obj with the same structure
]
and I need to fetch information about the server versions via api calls to the url (or ip) and return an array of objects that looks like this:
[
{
name: "Server A",
version: "1.0.1",
subVersions: [
{
name: "Subversion A1",
version: "1.0.0"
},
{
name: "Subversion A2",
version: "1.0.0"
},
]
}
]
I'm doing is the following: the fetch() method will call fetchVersion() (which returns the main server version), and then it maps inside all the subVersions to fetch them too.
I'm struggling to get the result.data of the subVersions fetch out of that nested map you can see below.
I've tried to:
return the data at every iteration
pushing the data inside an array and try to return it at the end of the iterations
returning the array of data or returning a new Promise that resolves the array of data
But nothing. I can see the right data at the most nested map, but outside I either get a
Promise { <pending> } array or an empty one.
Sorry if the code looks messy, I hope it makes sense.
const fetchVersion = server =>
axios
.get(server.url, {
headers: {
Authorization: `Bearer ${server.accessToken}`,
"Content-Type": "application/json"
},
timeout: 30000
})
.then(result => new Promise(resolve => resolve(result.data)));
const fetchSubVersion = subVersion =>
axios
.get(subVersion.ip, {
headers: {
Authorization: `Bearer ${subVersion.accessToken}`,
"Content-Type": "application/json"
},
timeout: 30000
})
.then(result => new Promise(resolve => resolve(result.data)));
Class Servers {
constructor(servers = []) {
this.servers = servers ;
}
fetch() {
// ==== this map below is the problematic part =====
const subVersions = this.servers.map(server => {
var subVersArr = server.subVersions.map(
async server.subVersions.map(subVersion =>
await fetchSubVersions(subVersion)
.catch(() => ({ data: {} }))
.then(result => new Promise(resolve => resolve(result.data)));
});
})
);
return Promise.all(subVersArr)
.catch(() => ({ data: {} }))
.then(data => {
console.log("data", data); // <- I see the data here correctly
return data;
});
};
// ======= till here ============
const fetches = this.servers.map(server =>
fetchVersion(server)
.catch(() => ({ data: {} }))
.then(result => {
console.log("subVersions", subVersions(server)); <- but not here
return {
name: server.name,
versions: result.data,
subVersions: subVersions(server), // should contain the result of the
problematic map above
}
}))
);
return Promise.all(fetches);
}
Thanks for the help!!
How are you calling your fetch function? You should await it wherever you are calling it.
Like this:
async dummyFunction() {
await fetch();
}
I finally figured it out. What I didn't have clear is that then() returns a promise itself, so all I had to just do subVersion: await subVersions(server) and the data is there.

Prisma2: How to solve n +1 Problem with Paljs

thx for any help.
Im using at the frontend the apollo-client and at the backend graphql-nexus,prisma2 and graphql-yoga server.
I want to solve the n + 1 problem with #paljs/plugins.
At the frontend I have a query posts like:
query posts{
posts {
id
favoritedBy(where: { id: { equals: $currentUserId } }) {
id
}
author {
id
avatar {
id
}
}
link {
id
}
games {
id
}
tags {
id
}
likes(where: { user: { id: { equals: $currentUserId } } }) {
id
}
}
}
Posts resolver:
import { PrismaSelect } from '#paljs/plugins'
export const posts = queryField('posts', {
type: 'Post',
list: true,
args: {
...
},
resolve: async (_parent, args, { prisma, request }, info) => {
const select = new PrismaSelect(info).value
let opArgs: FindManyPostArgs = {
take: 10,
orderBy: {
[args.orderBy]: 'desc',
},
...select
}
const post = await prisma.post.findMany(opArgs)
//The result I want to return with the "sub-models" like likes, author tags...
console.log(JSON.stringify(post, undefined, 2))
return post
},
})
I logging the queries
const prisma = new PrismaClient({
log: ['query'],
})
My Problem: With PrismaSelect, I have 5 queries more than without and If I check the request-time at the frontend I need 300-400ms longer with PrismaSelect. So what I'm doing wrong?
I saw in the #paljs/plugins doc the select in the context. Maybe that is my mistake. How can I use the select in the context?
Here ist my Context:
import { PrismaClient, PrismaClientOptions } from '#prisma/client'
import { PubSub } from 'graphql-yoga'
import { PrismaDelete, onDeleteArgs } from '#paljs/plugins'
class Prisma extends PrismaClient {
constructor(options?: PrismaClientOptions) {
super(options)
}
async onDelete(args: onDeleteArgs) {
const prismaDelete = new PrismaDelete(this)
await prismaDelete.onDelete(args)
}
}
export const prisma = new PrismaClient({
log: ['query'],
})
export const pubsub = new PubSub()
export interface Context {
prisma: PrismaClient
request: any
pubsub: PubSub
}
export function createContext(request: any): Context {
return { prisma, request, pubsub }
}
You need to know that to use my PrismaSelect plugin you need to remove the nexus-prisma-plugin package and use my Pal.js CLI to create your CRUD and ObjectType for nexus and using #paljs/nexus plugin to add in mackSchema function
import { makeSchema } from '#nexus/schema';
import * as types from './graphql';
import { paljs } from '#paljs/nexus'; // import our plugin
export const schema = makeSchema({
types,
plugins: [paljs()],// here our plugin don't use nexus-prisma-plugin
outputs: {
schema: __dirname + '/generated/schema.graphql',
typegen: __dirname + '/generated/nexus.ts',
},
typegenAutoConfig: {
sources: [
{
source: require.resolve('./context'),
alias: 'Context',
},
],
contextType: 'Context.Context',
},
});
Now add this type to your Context
export interface Context {
prisma: PrismaClient
request: any
pubsub: PubSub
select: any // here our select type
}
export function createContext(request: any): Context {
// our paljs plugin will add select object before resolver
return { prisma, request, pubsub, select: {} }
}
after you add our plugin your query will log like this
extendType({
type: 'Query',
definition(t) {
t.field('findOneUser', {
type: 'User',
nullable: true,
args: {
where: arg({
type: 'UserWhereUniqueInput',
nullable: false,
}),
},
resolve(_, { where }, { prisma, select }) {
// our plugin add select object into context for you
return prisma.user.findOne({
where,
...select,
});
},
});
},
});
Can you please try to use my pal c command to start an example from my list and try your schema and make tests with it
It is working, thx Ahmed your plugin is AWESOME!!!!!
I changed my Post-Object from
const Post = objectType({
name: 'Post',
definition(t) {
t.model.id()
t.model.authorId()
t.model.tags()
t.model.games()
t.model.link()
t.model.report()
t.model.notifications()
t.model.author()
t.model.favoritedBy({
filtering: {
id: true,
},
})
t.model.likes({
filtering: {
user: true,
},
})
}
})
to
const Post = objectType({
name: 'Post',
definition(t) {
t.string('id')
t.field('tags', {
nullable: false,
list: [true],
type: 'Tag',
resolve(parent: any) {
return parent['tags']
},
})
t.field('games', {
list: [true],
type: 'Game',
resolve(parent: any) {
return parent['games']
},
})
t.field('link', {
type: 'Link',
nullable: true,
resolve(parent: any) {
return parent['link']
},
})
t.field('notifications', {
list: [true],
type: 'Notification',
resolve(parent: any) {
return parent['notifications']
},
})
t.field('author', {
nullable: false,
type: 'User',
resolve(parent: any) {
return parent['author']
},
})
t.field('favoritedBy', {
nullable: false,
list: [true],
type: 'User',
args: {
where: 'UserWhereInput',
},
resolve(parent: any) {
return parent['favoritedBy']
},
})
t.field('likes', {
list: [true],
type: 'Like',
args: {
where: 'LikeWhereInput',
},
resolve(parent: any) {
return parent['likes']
},
})
},
})
And I also used the nexus-prisma-plugin and paljs-plugin at the same time

How to redirect after a deleting mutation in Apollo?

After I delete a post, I want to update the cache and redirect to the post index page.
deletePost() {
this.$apollo.mutate({
mutation: DELETE_POST,
variables: {
postId: this.postId
},
update: (cache, { data: { deletePost } }) => {
const query = {
query: GET_PAGINATED_POSTS,
variables: {
page: 0,
pageSize: 10
},
};
const data = cache.readQuery({ ...query });
data.postsPage = data.postsPage.filter(post => post._id != this.postId)
cache.writeQuery({ ...query, data })
}
})
// redirect
this.$router.push({ name: 'IndexPosts' })
}
The above works, but since I'm not doing an optimisticResponse, there's a bit of a delay between the time the index page shows and the time the cache update takes place. How can I solve this? I was trying to do an optimisticResponse but I don't know how to get the list of paginated posts without doing another query.
this.$apollo.mutate(...) returns a promise.
Try something like:
this.$apollo.mutate(...)
.then(({ data: { deletePost } }) => {
this.$router.push({ name: 'IndexPosts' })
})

Resources