fix(repository): Move backend code into subdirectory

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-10-02 20:10:32 +02:00 committed by David Mehren
parent 86584e705f
commit bf30cbcf48
272 changed files with 87 additions and 67 deletions

View file

@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsString } from 'class-validator';
import { BaseDto } from '../utils/base.dto.';
export class GroupInfoDto extends BaseDto {
/**
* Name of the group
* @example "superheroes"
*/
@IsString()
@ApiProperty()
name: string;
/**
* Display name of this group
* @example "Superheroes"
*/
@IsString()
@ApiProperty()
displayName: string;
/**
* True if this group must be specially handled
* Used for e.g. "everybody", "all logged in users"
* @example false
*/
@IsBoolean()
@ApiProperty()
special: boolean;
}

View file

@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
Entity,
JoinTable,
ManyToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { User } from '../users/user.entity';
@Entity()
export class Group {
@PrimaryGeneratedColumn()
id: number;
@Column({
unique: true,
})
name: string;
@Column()
displayName: string;
/**
* Is set to denote a special group
* Special groups are used to map the old share settings like "everyone can edit"
* or "logged in users can view" to the group permission system
*/
@Column()
special: boolean;
@ManyToMany((_) => User, (user) => user.groups, {
eager: true,
})
@JoinTable()
members: Promise<User[]>;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
public static create(
name: string,
displayName: string,
special: boolean,
): Omit<Group, 'id'> {
const newGroup = new Group();
newGroup.name = name;
newGroup.displayName = displayName;
newGroup.special = special; // this attribute should only be true for the two special groups
newGroup.members = Promise.resolve([]);
return newGroup;
}
}

View file

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LoggerModule } from '../logger/logger.module';
import { Group } from './group.entity';
import { GroupsService } from './groups.service';
@Module({
imports: [TypeOrmModule.forFeature([Group]), LoggerModule],
providers: [GroupsService],
exports: [GroupsService],
})
export class GroupsModule {}

View file

@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ConfigModule } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import appConfigMock from '../config/mock/app.config.mock';
import { AlreadyInDBError, NotInDBError } from '../errors/errors';
import { LoggerModule } from '../logger/logger.module';
import { Group } from './group.entity';
import { GroupsService } from './groups.service';
import { SpecialGroup } from './groups.special';
describe('GroupsService', () => {
let service: GroupsService;
let groupRepo: Repository<Group>;
let group: Group;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
GroupsService,
{
provide: getRepositoryToken(Group),
useClass: Repository,
},
],
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [appConfigMock],
}),
LoggerModule,
],
}).compile();
service = module.get<GroupsService>(GroupsService);
groupRepo = module.get<Repository<Group>>(getRepositoryToken(Group));
group = Group.create('testGroup', 'Superheros', false) as Group;
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('createGroup', () => {
const groupName = 'testGroup';
const displayname = 'Group Test';
beforeEach(() => {
jest
.spyOn(groupRepo, 'save')
.mockImplementationOnce(async (group: Group): Promise<Group> => group);
});
it('successfully creates a group', async () => {
const user = await service.createGroup(groupName, displayname);
expect(user.name).toEqual(groupName);
expect(user.displayName).toEqual(displayname);
});
it('fails if group name is already taken', async () => {
// add additional mock implementation for failure
jest.spyOn(groupRepo, 'save').mockImplementationOnce(() => {
throw new Error();
});
// create first group with group name
await service.createGroup(groupName, displayname);
// attempt to create second group with group name
await expect(service.createGroup(groupName, displayname)).rejects.toThrow(
AlreadyInDBError,
);
});
});
describe('getGroupByName', () => {
it('works', async () => {
jest.spyOn(groupRepo, 'findOne').mockResolvedValueOnce(group);
const foundGroup = await service.getGroupByName(group.name);
expect(foundGroup.name).toEqual(group.name);
expect(foundGroup.displayName).toEqual(group.displayName);
expect(foundGroup.special).toEqual(group.special);
});
it('fails with non-existing group', async () => {
jest.spyOn(groupRepo, 'findOne').mockResolvedValueOnce(null);
await expect(service.getGroupByName('i_dont_exist')).rejects.toThrow(
NotInDBError,
);
});
});
it('getEveryoneGroup return EVERYONE group', async () => {
const spy = jest.spyOn(service, 'getGroupByName').mockImplementation();
await service.getEveryoneGroup();
expect(spy).toHaveBeenCalledWith(SpecialGroup.EVERYONE);
});
it('getLoggedInGroup return LOGGED_IN group', async () => {
const spy = jest.spyOn(service, 'getGroupByName').mockImplementation();
await service.getLoggedInGroup();
expect(spy).toHaveBeenCalledWith(SpecialGroup.LOGGED_IN);
});
describe('toGroupDto', () => {
it('works', () => {
const groupDto = service.toGroupDto(group);
expect(groupDto.displayName).toEqual(group.displayName);
expect(groupDto.name).toEqual(group.name);
expect(groupDto.special).toBeFalsy();
});
});
});

View file

@ -0,0 +1,98 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AlreadyInDBError, NotInDBError } from '../errors/errors';
import { ConsoleLoggerService } from '../logger/console-logger.service';
import { GroupInfoDto } from './group-info.dto';
import { Group } from './group.entity';
import { SpecialGroup } from './groups.special';
@Injectable()
export class GroupsService {
constructor(
private readonly logger: ConsoleLoggerService,
@InjectRepository(Group) private groupRepository: Repository<Group>,
) {
this.logger.setContext(GroupsService.name);
}
/**
* @async
* Create a new group with a given name and displayName
* @param name - the group name the new group shall have
* @param displayName - the display name the new group shall have
* @param special - if the group is special or not
* @return {Group} the group
* @throws {AlreadyInDBError} the group name is already taken.
*/
async createGroup(
name: string,
displayName: string,
special = false,
): Promise<Group> {
const group = Group.create(name, displayName, special);
try {
return await this.groupRepository.save(group);
} catch {
this.logger.debug(
`A group with the name '${name}' already exists.`,
'createGroup',
);
throw new AlreadyInDBError(
`A group with the name '${name}' already exists.`,
);
}
}
/**
* @async
* Get a group by their name.
* @param {string} name - the groups name
* @return {Group} the group
* @throws {NotInDBError} there is no group with this name
*/
async getGroupByName(name: string): Promise<Group> {
const group = await this.groupRepository.findOne({
where: { name: name },
});
if (group === null) {
throw new NotInDBError(`Group with name '${name}' not found`);
}
return group;
}
/**
* Get the group object for the everyone special group.
* @return {Group} the EVERYONE group
*/
getEveryoneGroup(): Promise<Group> {
return this.getGroupByName(SpecialGroup.EVERYONE);
}
/**
* Get the group object for the logged-in special group.
* @return {Group} the LOGGED_IN group
*/
getLoggedInGroup(): Promise<Group> {
return this.getGroupByName(SpecialGroup.LOGGED_IN);
}
/**
* Build GroupInfoDto from a group.
* @param {Group} group - the group to use
* @return {GroupInfoDto} the built GroupInfoDto
*/
toGroupDto(group: Group): GroupInfoDto {
return {
name: group.name,
displayName: group.displayName,
special: group.special,
};
}
}

View file

@ -0,0 +1,10 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
export enum SpecialGroup {
LOGGED_IN = '_LOGGED_IN',
EVERYONE = '_EVERYONE',
}