mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-31 15:18:38 -04:00
refactor: move typeorm store into new session module
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
14ee7485ad
commit
57365bb727
16 changed files with 204 additions and 49 deletions
|
@ -8,11 +8,11 @@ import { NestExpressApplication } from '@nestjs/platform-express';
|
|||
|
||||
import { AppConfig } from './config/app.config';
|
||||
import { AuthConfig } from './config/auth.config';
|
||||
import { DatabaseConfig } from './config/database.config';
|
||||
import { MediaConfig } from './config/media.config';
|
||||
import { ErrorExceptionMapping } from './errors/error-mapping';
|
||||
import { ConsoleLoggerService } from './logger/console-logger.service';
|
||||
import { BackendType } from './media/backends/backend-type.enum';
|
||||
import { SessionService } from './session/session.service';
|
||||
import { setupSpecialGroups } from './utils/createSpecialGroups';
|
||||
import { setupFrontendProxy } from './utils/frontend-integration';
|
||||
import { setupSessionMiddleware } from './utils/session';
|
||||
|
@ -26,7 +26,6 @@ export async function setupApp(
|
|||
app: NestExpressApplication,
|
||||
appConfig: AppConfig,
|
||||
authConfig: AuthConfig,
|
||||
databaseConfig: DatabaseConfig,
|
||||
mediaConfig: MediaConfig,
|
||||
logger: ConsoleLoggerService,
|
||||
): Promise<void> {
|
||||
|
@ -48,7 +47,11 @@ export async function setupApp(
|
|||
|
||||
await setupSpecialGroups(app);
|
||||
|
||||
setupSessionMiddleware(app, authConfig, databaseConfig);
|
||||
setupSessionMiddleware(
|
||||
app,
|
||||
authConfig,
|
||||
app.get(SessionService).getTypeormStore(),
|
||||
);
|
||||
|
||||
app.enableCors({
|
||||
origin: appConfig.rendererOrigin,
|
||||
|
|
|
@ -34,6 +34,7 @@ import { MonitoringModule } from './monitoring/monitoring.module';
|
|||
import { NotesModule } from './notes/notes.module';
|
||||
import { PermissionsModule } from './permissions/permissions.module';
|
||||
import { RevisionsModule } from './revisions/revisions.module';
|
||||
import { SessionModule } from './session/session.module';
|
||||
import { UsersModule } from './users/users.module';
|
||||
|
||||
const routes: Routes = [
|
||||
|
@ -101,6 +102,7 @@ const routes: Routes = [
|
|||
AuthModule,
|
||||
FrontendConfigModule,
|
||||
IdentityModule,
|
||||
SessionModule,
|
||||
],
|
||||
controllers: [],
|
||||
providers: [FrontendConfigService],
|
||||
|
|
|
@ -6,11 +6,12 @@
|
|||
import { ConfigModule } from '@nestjs/config';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getDataSourceToken, getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { DataSource, EntityManager, Repository } from 'typeorm';
|
||||
|
||||
import { AuthToken } from '../auth/auth-token.entity';
|
||||
import { Author } from '../authors/author.entity';
|
||||
import appConfigMock from '../config/mock/app.config.mock';
|
||||
import databaseConfigMock from '../config/mock/database.config.mock';
|
||||
import noteConfigMock from '../config/mock/note.config.mock';
|
||||
import { NotInDBError } from '../errors/errors';
|
||||
import { Group } from '../groups/group.entity';
|
||||
|
@ -48,6 +49,16 @@ describe('HistoryService', () => {
|
|||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
noteRepo = new Repository<Note>(
|
||||
'',
|
||||
new EntityManager(
|
||||
new DataSource({
|
||||
type: 'sqlite',
|
||||
database: ':memory:',
|
||||
}),
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
HistoryService,
|
||||
|
@ -61,7 +72,7 @@ describe('HistoryService', () => {
|
|||
},
|
||||
{
|
||||
provide: getRepositoryToken(Note),
|
||||
useClass: Repository,
|
||||
useValue: noteRepo,
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
|
@ -70,7 +81,7 @@ describe('HistoryService', () => {
|
|||
NotesModule,
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
load: [appConfigMock, noteConfigMock],
|
||||
load: [appConfigMock, databaseConfigMock, noteConfigMock],
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
@ -85,7 +96,7 @@ describe('HistoryService', () => {
|
|||
.overrideProvider(getRepositoryToken(Revision))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Note))
|
||||
.useClass(Repository)
|
||||
.useValue(noteRepo)
|
||||
.overrideProvider(getRepositoryToken(Tag))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(NoteGroupPermission))
|
||||
|
|
14
src/main.ts
14
src/main.ts
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -31,24 +31,16 @@ async function bootstrap(): Promise<void> {
|
|||
// Initialize config and abort if we don't have a valid config
|
||||
const configService = app.get(ConfigService);
|
||||
const appConfig = configService.get<AppConfig>('appConfig');
|
||||
const databaseConfig = configService.get<DatabaseConfig>('databaseConfig');
|
||||
const authConfig = configService.get<AuthConfig>('authConfig');
|
||||
const mediaConfig = configService.get<MediaConfig>('mediaConfig');
|
||||
if (!appConfig || !databaseConfig || !authConfig || !mediaConfig) {
|
||||
if (!appConfig || !authConfig || !mediaConfig) {
|
||||
logger.error('Could not initialize config, aborting.', 'AppBootstrap');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Call common setup function which handles the rest
|
||||
// Setup code must be added there!
|
||||
await setupApp(
|
||||
app,
|
||||
appConfig,
|
||||
authConfig,
|
||||
databaseConfig,
|
||||
mediaConfig,
|
||||
logger,
|
||||
);
|
||||
await setupApp(app, appConfig, authConfig, mediaConfig, logger);
|
||||
|
||||
// Start the server
|
||||
await app.listen(appConfig.port);
|
||||
|
|
|
@ -12,6 +12,7 @@ import { Repository } from 'typeorm';
|
|||
import appConfigMock from '../../src/config/mock/app.config.mock';
|
||||
import { AuthToken } from '../auth/auth-token.entity';
|
||||
import { Author } from '../authors/author.entity';
|
||||
import databaseConfigMock from '../config/mock/database.config.mock';
|
||||
import mediaConfigMock from '../config/mock/media.config.mock';
|
||||
import noteConfigMock from '../config/mock/note.config.mock';
|
||||
import { ClientError, NotInDBError } from '../errors/errors';
|
||||
|
@ -52,7 +53,12 @@ describe('MediaService', () => {
|
|||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
load: [mediaConfigMock, appConfigMock, noteConfigMock],
|
||||
load: [
|
||||
mediaConfigMock,
|
||||
appConfigMock,
|
||||
databaseConfigMock,
|
||||
noteConfigMock,
|
||||
],
|
||||
}),
|
||||
LoggerModule,
|
||||
NotesModule,
|
||||
|
|
|
@ -6,11 +6,12 @@
|
|||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { DataSource, EntityManager, Repository } from 'typeorm';
|
||||
|
||||
import { AuthToken } from '../auth/auth-token.entity';
|
||||
import { Author } from '../authors/author.entity';
|
||||
import appConfigMock from '../config/mock/app.config.mock';
|
||||
import databaseConfigMock from '../config/mock/database.config.mock';
|
||||
import noteConfigMock from '../config/mock/note.config.mock';
|
||||
import {
|
||||
AlreadyInDBError,
|
||||
|
@ -43,13 +44,23 @@ describe('AliasService', () => {
|
|||
let aliasRepo: Repository<Alias>;
|
||||
let forbiddenNoteId: string;
|
||||
beforeEach(async () => {
|
||||
noteRepo = new Repository<Note>(
|
||||
'',
|
||||
new EntityManager(
|
||||
new DataSource({
|
||||
type: 'sqlite',
|
||||
database: ':memory:',
|
||||
}),
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
AliasService,
|
||||
NotesService,
|
||||
{
|
||||
provide: getRepositoryToken(Note),
|
||||
useClass: Repository,
|
||||
useValue: noteRepo,
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(Alias),
|
||||
|
@ -67,7 +78,7 @@ describe('AliasService', () => {
|
|||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
load: [appConfigMock, noteConfigMock],
|
||||
load: [appConfigMock, databaseConfigMock, noteConfigMock],
|
||||
}),
|
||||
LoggerModule,
|
||||
UsersModule,
|
||||
|
@ -77,7 +88,7 @@ describe('AliasService', () => {
|
|||
],
|
||||
})
|
||||
.overrideProvider(getRepositoryToken(Note))
|
||||
.useClass(Repository)
|
||||
.useValue(noteRepo)
|
||||
.overrideProvider(getRepositoryToken(Tag))
|
||||
.useClass(Repository)
|
||||
.overrideProvider(getRepositoryToken(Alias))
|
||||
|
|
|
@ -11,6 +11,7 @@ import { DataSource, EntityManager, Repository } from 'typeorm';
|
|||
import { AuthToken } from '../auth/auth-token.entity';
|
||||
import { Author } from '../authors/author.entity';
|
||||
import appConfigMock from '../config/mock/app.config.mock';
|
||||
import databaseConfigMock from '../config/mock/database.config.mock';
|
||||
import noteConfigMock from '../config/mock/note.config.mock';
|
||||
import {
|
||||
AlreadyInDBError,
|
||||
|
@ -135,13 +136,23 @@ describe('NotesService', () => {
|
|||
),
|
||||
undefined,
|
||||
);
|
||||
noteRepo = new Repository<Note>(
|
||||
'',
|
||||
new EntityManager(
|
||||
new DataSource({
|
||||
type: 'sqlite',
|
||||
database: ':memory:',
|
||||
}),
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
NotesService,
|
||||
AliasService,
|
||||
{
|
||||
provide: getRepositoryToken(Note),
|
||||
useClass: Repository,
|
||||
useValue: noteRepo,
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(Tag),
|
||||
|
@ -157,18 +168,18 @@ describe('NotesService', () => {
|
|||
},
|
||||
],
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
load: [appConfigMock, noteConfigMock],
|
||||
}),
|
||||
LoggerModule,
|
||||
UsersModule,
|
||||
GroupsModule,
|
||||
RevisionsModule,
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
load: [appConfigMock, databaseConfigMock, noteConfigMock],
|
||||
}),
|
||||
],
|
||||
})
|
||||
.overrideProvider(getRepositoryToken(Note))
|
||||
.useClass(Repository)
|
||||
.useValue(noteRepo)
|
||||
.overrideProvider(getRepositoryToken(Tag))
|
||||
.useClass(Repository)
|
||||
.overrideProvider(getRepositoryToken(Alias))
|
||||
|
|
|
@ -11,6 +11,7 @@ import { DataSource, EntityManager, Repository } from 'typeorm';
|
|||
import { AuthToken } from '../auth/auth-token.entity';
|
||||
import { Author } from '../authors/author.entity';
|
||||
import appConfigMock from '../config/mock/app.config.mock';
|
||||
import databaseConfigMock from '../config/mock/database.config.mock';
|
||||
import noteConfigMock from '../config/mock/note.config.mock';
|
||||
import { PermissionsUpdateInconsistentError } from '../errors/errors';
|
||||
import { Group } from '../groups/group.entity';
|
||||
|
@ -86,17 +87,13 @@ describe('PermissionsService', () => {
|
|||
},
|
||||
],
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
load: [appConfigMock],
|
||||
}),
|
||||
LoggerModule,
|
||||
PermissionsModule,
|
||||
UsersModule,
|
||||
NotesModule,
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
load: [appConfigMock, noteConfigMock],
|
||||
load: [appConfigMock, databaseConfigMock, noteConfigMock],
|
||||
}),
|
||||
GroupsModule,
|
||||
],
|
||||
|
|
|
@ -11,6 +11,7 @@ import { Repository } from 'typeorm';
|
|||
import { AuthToken } from '../auth/auth-token.entity';
|
||||
import { Author } from '../authors/author.entity';
|
||||
import appConfigMock from '../config/mock/app.config.mock';
|
||||
import databaseConfigMock from '../config/mock/database.config.mock';
|
||||
import noteConfigMock from '../config/mock/note.config.mock';
|
||||
import { NotInDBError } from '../errors/errors';
|
||||
import { Group } from '../groups/group.entity';
|
||||
|
@ -48,7 +49,7 @@ describe('RevisionsService', () => {
|
|||
LoggerModule,
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
load: [appConfigMock, noteConfigMock],
|
||||
load: [appConfigMock, databaseConfigMock, noteConfigMock],
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
|
17
src/session/session.module.ts
Normal file
17
src/session/session.module.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { Session } from '../users/session.entity';
|
||||
import { SessionService } from './session.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Session])],
|
||||
exports: [SessionService],
|
||||
providers: [SessionService],
|
||||
})
|
||||
export class SessionModule {}
|
55
src/session/session.service.spec.ts
Normal file
55
src/session/session.service.spec.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import * as ConnectTypeormModule from 'connect-typeorm';
|
||||
import { TypeormStore } from 'connect-typeorm';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { DatabaseType } from '../config/database-type.enum';
|
||||
import { DatabaseConfig } from '../config/database.config';
|
||||
import { Session } from '../users/session.entity';
|
||||
import { SessionService } from './session.service';
|
||||
|
||||
jest.mock('cookie');
|
||||
jest.mock('cookie-signature');
|
||||
|
||||
describe('SessionService', () => {
|
||||
let mockedTypeormStore: TypeormStore;
|
||||
let mockedSessionRepository: Repository<Session>;
|
||||
let databaseConfigMock: DatabaseConfig;
|
||||
let typeormStoreConstructorMock: jest.SpyInstance;
|
||||
let sessionService: SessionService;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
jest.restoreAllMocks();
|
||||
mockedTypeormStore = Mock.of<TypeormStore>({
|
||||
connect: jest.fn(() => mockedTypeormStore),
|
||||
});
|
||||
mockedSessionRepository = Mock.of<Repository<Session>>({});
|
||||
databaseConfigMock = Mock.of<DatabaseConfig>({
|
||||
type: DatabaseType.SQLITE,
|
||||
});
|
||||
|
||||
typeormStoreConstructorMock = jest
|
||||
.spyOn(ConnectTypeormModule, 'TypeormStore')
|
||||
.mockReturnValue(mockedTypeormStore);
|
||||
|
||||
sessionService = new SessionService(
|
||||
mockedSessionRepository,
|
||||
databaseConfigMock,
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a new TypeormStore on create', () => {
|
||||
expect(typeormStoreConstructorMock).toBeCalledWith({
|
||||
cleanupLimit: 2,
|
||||
limitSubquery: true,
|
||||
});
|
||||
expect(mockedTypeormStore.connect).toBeCalledWith(mockedSessionRepository);
|
||||
expect(sessionService.getTypeormStore()).toBe(mockedTypeormStore);
|
||||
});
|
||||
});
|
35
src/session/session.service.ts
Normal file
35
src/session/session.service.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { TypeormStore } from 'connect-typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { DatabaseType } from '../config/database-type.enum';
|
||||
import databaseConfiguration, {
|
||||
DatabaseConfig,
|
||||
} from '../config/database.config';
|
||||
import { Session } from '../users/session.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SessionService {
|
||||
private readonly typeormStore: TypeormStore;
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Session) private sessionRepository: Repository<Session>,
|
||||
@Inject(databaseConfiguration.KEY)
|
||||
private dbConfig: DatabaseConfig,
|
||||
) {
|
||||
this.typeormStore = new TypeormStore({
|
||||
cleanupLimit: 2,
|
||||
limitSubquery: dbConfig.type !== DatabaseType.MARIADB,
|
||||
}).connect(sessionRepository);
|
||||
}
|
||||
|
||||
getTypeormStore(): TypeormStore {
|
||||
return this.typeormStore;
|
||||
}
|
||||
}
|
|
@ -4,40 +4,34 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { TypeormStore } from 'connect-typeorm';
|
||||
import session from 'express-session';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { AuthConfig } from '../config/auth.config';
|
||||
import { DatabaseType } from '../config/database-type.enum';
|
||||
import { DatabaseConfig } from '../config/database.config';
|
||||
import { Session } from '../users/session.entity';
|
||||
|
||||
export const HEDGEDOC_SESSION = 'hedgedoc-session';
|
||||
|
||||
/**
|
||||
* Setup the session middleware via the given authConfig.
|
||||
* Set up the session middleware via the given authConfig.
|
||||
* @param {INestApplication} app - the nest application to configure the middleware for.
|
||||
* @param {AuthConfig} authConfig - the authConfig to configure the middleware with.
|
||||
* @param {DatabaseConfig} dbConfig - the DatabaseConfig to configure the middleware with.
|
||||
* @param {TypeormStore} typeormStore - the typeormStore to handle session data.
|
||||
*/
|
||||
export function setupSessionMiddleware(
|
||||
app: INestApplication,
|
||||
authConfig: AuthConfig,
|
||||
dbConfig: DatabaseConfig,
|
||||
typeormStore: TypeormStore,
|
||||
): void {
|
||||
app.use(
|
||||
session({
|
||||
name: 'hedgedoc-session',
|
||||
name: HEDGEDOC_SESSION,
|
||||
secret: authConfig.session.secret,
|
||||
cookie: {
|
||||
maxAge: authConfig.session.lifetime,
|
||||
},
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
store: new TypeormStore({
|
||||
cleanupLimit: 2,
|
||||
limitSubquery: dbConfig.type !== DatabaseType.MARIADB,
|
||||
}).connect(app.get<Repository<Session>>(getRepositoryToken(Session))),
|
||||
store: typeormStore,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue