diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 100% rename from .eslintrc.js rename to .eslintrc.cjs diff --git a/.gitignore b/.gitignore index 205bb62b3..37699ce8c 100644 --- a/.gitignore +++ b/.gitignore @@ -89,8 +89,9 @@ typings/ .next # Nuxt.js build / generate output -.nuxt -dist +.nuxt/ +.output/ +dist/ # Gatsby files .cache/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 1a6a59611..83aae4ba9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -47,5 +47,13 @@ ".vue", ] }, + "search.exclude": { + "**/node_modules": true, + "**/bower_components": true, + "**/*.code-search": true, + "**/coverage": true, + "**/.output": true, + "**/.nuxt": true, + }, "vscode-graphql.useSchemaFileDefinitions": true } diff --git a/README.md b/README.md index 425fda798..98901aad9 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Now close and re-open the workspace. | yarn dev | Start ExpressJS server in development with Nuxt.js in dev mode with hot reloading enabled. Listen on [http://localhost:3000](http://localhost:3000). The GraphQL API is then accessible at [http://localhost:3000/api](http://localhost:3000/api) | | yarn test | Execute all tests. Pass `-u` to update all Jest snapshots.| | yarn build | Build the nuxt.js web application for production. | -| yarn start | Start ExpressJS server in production. | +| yarn start | Start ExpressJS server (for testing purposes). | | yarn prisma:studio | Explore data in the database using a visual editor. | | yarn storybook | Start [Storybook](#ui-workflow-storybook) in your browser. | diff --git a/api/database/seed.ts b/api/database/seed.ts index 3fd87e65a..d20a912db 100644 --- a/api/database/seed.ts +++ b/api/database/seed.ts @@ -13,7 +13,7 @@ async function seedInternal(prisma: PrismaClientT): Promise { id: 'ckn4oul7100004cv7y3t94n8j', email: 'alice@jabref.de', password: - 'saltsaltsaltsaltsaltsaltsaltsalt63f7e072b6a9faf6e77616c098c4bb3ac69c58d249e620e1dd51257018ac7fcb40b576e9f69e9c556c70a980327dac12b1ee76a76f22b249d585fe2de10b365a', // EBNPXY35TYkYXHs + '19184d8c1c1e9b483d8347f8da0d53ad92170233100d32c3a0d748725948c28d09a060d7f02962b7b93320c72a2cdd94f21b16b08bf8bd1cba0c5f77afeffddbb24df527c4f16f1fca6eb5480159b56df3d818b4b3c74ead04227a78b3d810b8', // EBNPXY35TYkYXHs name: 'Alice', }, }) diff --git a/api/documents/user.document.service.ts b/api/documents/user.document.service.ts index e7c70d3b4..540788fde 100644 --- a/api/documents/user.document.service.ts +++ b/api/documents/user.document.service.ts @@ -1,5 +1,5 @@ -import type { PrismaClient } from '@prisma/client' -import { +import type { + PrismaClient, UserDocument as PlainUserDocument, Prisma, User, diff --git a/api/groups/service.ts b/api/groups/service.ts index 437b4457a..c15204030 100644 --- a/api/groups/service.ts +++ b/api/groups/service.ts @@ -1,14 +1,9 @@ -import type { - PrismaClient as PrismaClientT, - Group, - User, - Prisma, -} from '@prisma/client' +import type { Group, Prisma, PrismaClient, User } from '@prisma/client' import { inject, injectable } from 'tsyringe' @injectable() export class GroupService { - constructor(@inject('PrismaClient') private prisma: PrismaClientT) {} + constructor(@inject('PrismaClient') private prisma: PrismaClient) {} async getGroupById(id: string): Promise { return await this.prisma.group.findUnique({ diff --git a/api/index.ts b/api/index.ts index d497e3912..aeaebb0da 100644 --- a/api/index.ts +++ b/api/index.ts @@ -1,16 +1,18 @@ import http from 'http' import express from 'express' import { ApolloServer } from 'apollo-server-express' -import './tsyringe.config' +import 'reflect-metadata' // Needed for tsyringe import { container } from 'tsyringe' import { ApolloServerPluginDrainHttpServer, ApolloServerPluginLandingPageLocalDefault, } from 'apollo-server-core' -import { config, Environment } from '../config' +import { Environment } from '../config' +import { configure as configureTsyringe } from './tsyringe.config' import { buildContext } from './context' import { loadSchema } from './schema' import PassportInitializer from './user/passport-initializer' +import config from '#config' // Create express instance const app = express() @@ -21,26 +23,29 @@ if (config.environment === Environment.Production) { } const httpServer = http.createServer(app) -const passportInitializer = container.resolve(PassportInitializer) -passportInitializer.initialize() -passportInitializer.install(app) +// TODO: Replace this with await, once esbuild supports top-level await +void configureTsyringe().then(() => { + const passportInitializer = container.resolve(PassportInitializer) + passportInitializer.initialize() + passportInitializer.install(app) -const server = new ApolloServer({ - schema: loadSchema(), - context: buildContext, - introspection: true, - plugins: [ - // Enable Apollo Studio in development, and also in production (at least for now) - ApolloServerPluginLandingPageLocalDefault({ footer: false }), - // Gracefully shutdown HTTP server when Apollo server terminates - ApolloServerPluginDrainHttpServer({ httpServer }), - ], -}) + const server = new ApolloServer({ + schema: loadSchema(), + context: buildContext, + introspection: true, + plugins: [ + // Enable Apollo Studio in development, and also in production (at least for now) + ApolloServerPluginLandingPageLocalDefault({ footer: false }), + // Gracefully shutdown HTTP server when Apollo server terminates + ApolloServerPluginDrainHttpServer({ httpServer }), + ], + }) -async function startServer() { - await server.start() - server.applyMiddleware({ app, path: '/' }) -} -void startServer() + async function startServer() { + await server.start() + server.applyMiddleware({ app, path: '/' }) + } + void startServer() +}) -module.exports = app +export default app diff --git a/api/tsyringe.config.ts b/api/tsyringe.config.ts index 3f4b50daa..82624e6fe 100644 --- a/api/tsyringe.config.ts +++ b/api/tsyringe.config.ts @@ -1,13 +1,15 @@ -import 'reflect-metadata' -import { PrismaClient } from '@prisma/client' - +import prisma from '@prisma/client' import { container, instanceCachingFactory } from 'tsyringe' import { createRedisClient } from './utils/services.factory' -container.register('PrismaClient', { - useFactory: instanceCachingFactory(() => new PrismaClient()), -}) +const { PrismaClient } = prisma + +export async function configure(): Promise { + container.register('PrismaClient', { + useFactory: instanceCachingFactory(() => new PrismaClient()), + }) -container.register('RedisClient', { - useValue: await createRedisClient(), -}) + container.register('RedisClient', { + useValue: await createRedisClient(), + }) +} diff --git a/api/user/passport-initializer.ts b/api/user/passport-initializer.ts index 93f8dc188..12ba41eb5 100644 --- a/api/user/passport-initializer.ts +++ b/api/user/passport-initializer.ts @@ -4,9 +4,10 @@ import session from 'express-session' import passport from 'passport' import { RedisClientType } from 'redis' import { inject, injectable } from 'tsyringe' -import { config, Environment } from '../../config' import { AuthService } from './auth.service' import EmailStrategy from './auth.email.strategy' +import config from '#config' +import { Environment } from '~/config' @injectable() export default class PassportInitializer { @@ -26,15 +27,23 @@ export default class PassportInitializer { } install(app: Express): void { + // TODO: Use redis store also for development as soon as https://github.com/tj/connect-redis/issues/336 is fixed (and mock-redis is compatible with redis v4) + let store + if (config.environment === Environment.Production) { + const RedisStore = connectRedis(session) + store = new RedisStore({ + client: this.redisClient, + disableTouch: true, + }) + } else { + store = new session.MemoryStore() + } + // Add middleware that sends and receives the session ID using cookies // See https://github.com/expressjs/session#readme - const RedisStore = connectRedis(session) app.use( session({ - store: new RedisStore({ - client: this.redisClient, - disableTouch: true, - }), + store, // The secret used to sign the session cookie secret: [config.session.primarySecret, config.session.secondarySecret], // Don't force session to be saved back to the session store unless it was modified diff --git a/api/user/resolvers.ts b/api/user/resolvers.ts index 3a64c6faa..593698a5d 100644 --- a/api/user/resolvers.ts +++ b/api/user/resolvers.ts @@ -78,7 +78,9 @@ export class Mutation { problems: [ { path: 'Email or Password', - message: (typeof info === 'string' ? info : info?.message) || 'Unknown error while logging in.', + message: + (typeof info === 'string' ? info : info?.message) || + 'Unknown error while logging in.', }, ], } diff --git a/api/utils/services.factory.ts b/api/utils/services.factory.ts index b30f8bcfe..ef0540fce 100644 --- a/api/utils/services.factory.ts +++ b/api/utils/services.factory.ts @@ -3,7 +3,8 @@ import { promisify } from 'util' import redis, { RedisClientType } from 'redis' import redisMock from 'redis-mock' -import { config, Environment } from '~/config' +import { Environment } from '~/config' +import config from '#config' export async function createRedisClient(): Promise> { if (config.environment === Environment.LocalDevelopment) { diff --git a/components/DetailPane.vue b/components/DetailPane.vue index 911847cb0..afbff9542 100644 --- a/components/DetailPane.vue +++ b/components/DetailPane.vue @@ -28,7 +28,7 @@ diff --git a/pages/user/forgot-password.vue b/pages/user/forgot-password.vue index 364874f44..5a72779e4 100644 --- a/pages/user/forgot-password.vue +++ b/pages/user/forgot-password.vue @@ -26,8 +26,7 @@