Add relation between User and Group

This represents the users which are members of this group

Signed-off-by: Yannick Bungers <git@innay.de>
This commit is contained in:
Yannick Bungers 2021-01-27 22:58:55 +01:00 committed by David Mehren
parent 8a33f75cf9
commit 68cbb5a9c2
No known key found for this signature in database
GPG key ID: 185982BA4C42B7C3
12 changed files with 103 additions and 16 deletions

View file

@ -119,6 +119,11 @@ entity "note_group_permission" {
*canEdit : boolean *canEdit : boolean
} }
entity "group_members_user" {
*group : number <<FK group>>
*member : uuid <<FK user>>
}
entity "tag" { entity "tag" {
*id: number <<generated>> *id: number <<generated>>
*name: text *name: text
@ -144,16 +149,16 @@ entity "history_entry" {
user "1" -- "0..*" note: owner user "1" -- "0..*" note: owner
user "1" -u- "1..*" identity user "1" -u- "1..*" identity
user "1" - "1..*" auth_token: authTokens user "1" -l- "1..*" auth_token: authTokens
user "1" -l- "1..*" session user "1" -r- "1..*" session
user "1" - "0..*" media_upload user "1" -- "0..*" media_upload
user "1" - "0..*" history_entry user "1" - "0..*" history_entry
user "0..*" -- "0..*" note user "0..*" -- "0..*" note
user "1" - "0..*" authorship user "1" -- "0..*" authorship
(user, note) . author_colors (user, note) . author_colors
revision "0..*" - "0..*" authorship revision "0..*" -- "0..*" authorship
(revision, authorship) .. revision_authorship (revision, authorship) .. revision_authorship
media_upload "0..*" -- "1" note media_upload "0..*" -- "1" note
@ -161,9 +166,11 @@ note "1" - "1..*" revision
note "1" - "0..*" history_entry note "1" - "0..*" history_entry
note "0..*" -l- "0..*" tag note "0..*" -l- "0..*" tag
note "0..*" -- "0..*" group note "0..*" -- "0..*" group
user "1..*" -- "0..*" group
user "0..*" -- "0..*" note user "0..*" -- "0..*" note
(user, note) . note_user_permission (user, note) . note_user_permission
(note, group) . note_group_permission (note, group) . note_group_permission
(user, group) . group_members_user
@enduml @enduml

View file

@ -20,6 +20,8 @@ import { User } from '../../../users/user.entity';
import { UsersModule } from '../../../users/users.module'; import { UsersModule } from '../../../users/users.module';
import { MeController } from './me.controller'; import { MeController } from './me.controller';
import { HistoryEntry } from '../../../history/history-entry.entity'; import { HistoryEntry } from '../../../history/history-entry.entity';
import { NoteGroupPermission } from '../../../permissions/note-group-permission.entity';
import { NoteUserPermission } from '../../../permissions/note-user-permission.entity';
describe('Me Controller', () => { describe('Me Controller', () => {
let controller: MeController; let controller: MeController;
@ -47,6 +49,10 @@ describe('Me Controller', () => {
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(HistoryEntry)) .overrideProvider(getRepositoryToken(HistoryEntry))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(NoteGroupPermission))
.useValue({})
.overrideProvider(getRepositoryToken(NoteUserPermission))
.useValue({})
.compile(); .compile();
controller = module.get<MeController>(MeController); controller = module.get<MeController>(MeController);

View file

@ -22,6 +22,8 @@ import { AuthToken } from '../../../auth/auth-token.entity';
import { Identity } from '../../../users/identity.entity'; import { Identity } from '../../../users/identity.entity';
import { User } from '../../../users/user.entity'; import { User } from '../../../users/user.entity';
import { MediaController } from './media.controller'; import { MediaController } from './media.controller';
import { NoteGroupPermission } from '../../../permissions/note-group-permission.entity';
import { NoteUserPermission } from '../../../permissions/note-user-permission.entity';
describe('Media Controller', () => { describe('Media Controller', () => {
let controller: MediaController; let controller: MediaController;
@ -57,6 +59,10 @@ describe('Media Controller', () => {
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(Tag)) .overrideProvider(getRepositoryToken(Tag))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(NoteGroupPermission))
.useValue({})
.overrideProvider(getRepositoryToken(NoteUserPermission))
.useValue({})
.compile(); .compile();
controller = module.get<MediaController>(MediaController); controller = module.get<MediaController>(MediaController);

View file

@ -19,8 +19,11 @@ import { Identity } from '../../../users/identity.entity';
import { User } from '../../../users/user.entity'; import { User } from '../../../users/user.entity';
import { UsersModule } from '../../../users/users.module'; import { UsersModule } from '../../../users/users.module';
import { NotesController } from './notes.controller'; import { NotesController } from './notes.controller';
import { PermissionsModule } from '../../../permissions/permissions.module';
import { HistoryModule } from '../../../history/history.module'; import { HistoryModule } from '../../../history/history.module';
import { HistoryEntry } from '../../../history/history-entry.entity'; import { HistoryEntry } from '../../../history/history-entry.entity';
import { NoteGroupPermission } from '../../../permissions/note-group-permission.entity';
import { NoteUserPermission } from '../../../permissions/note-user-permission.entity';
describe('Notes Controller', () => { describe('Notes Controller', () => {
let controller: NotesController; let controller: NotesController;
@ -39,7 +42,13 @@ describe('Notes Controller', () => {
useValue: {}, useValue: {},
}, },
], ],
imports: [RevisionsModule, UsersModule, LoggerModule, HistoryModule], imports: [
RevisionsModule,
UsersModule,
LoggerModule,
PermissionsModule,
HistoryModule,
],
}) })
.overrideProvider(getRepositoryToken(Note)) .overrideProvider(getRepositoryToken(Note))
.useValue({}) .useValue({})
@ -61,6 +70,10 @@ describe('Notes Controller', () => {
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(HistoryEntry)) .overrideProvider(getRepositoryToken(HistoryEntry))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(NoteGroupPermission))
.useValue({})
.overrideProvider(getRepositoryToken(NoteUserPermission))
.useValue({})
.compile(); .compile();
controller = module.get<NotesController>(NotesController); controller = module.get<NotesController>(NotesController);

View file

@ -4,7 +4,14 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; import {
Column,
Entity,
JoinTable,
ManyToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { User } from '../users/user.entity';
@Entity() @Entity()
export class Group { export class Group {
@ -26,4 +33,11 @@ export class Group {
*/ */
@Column() @Column()
special: boolean; special: boolean;
@ManyToMany((_) => User, (user) => user.groups, {
eager: true,
cascade: true,
})
@JoinTable()
members: User[];
} }

View file

@ -21,6 +21,8 @@ import { AuthToken } from '../auth/auth-token.entity';
import { Revision } from '../revisions/revision.entity'; import { Revision } from '../revisions/revision.entity';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { NotInDBError } from '../errors/errors'; import { NotInDBError } from '../errors/errors';
import { NoteGroupPermission } from '../permissions/note-group-permission.entity';
import { NoteUserPermission } from '../permissions/note-user-permission.entity';
describe('HistoryService', () => { describe('HistoryService', () => {
let service: HistoryService; let service: HistoryService;
@ -54,6 +56,10 @@ describe('HistoryService', () => {
.useClass(Repository) .useClass(Repository)
.overrideProvider(getRepositoryToken(Tag)) .overrideProvider(getRepositoryToken(Tag))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(NoteGroupPermission))
.useValue({})
.overrideProvider(getRepositoryToken(NoteUserPermission))
.useValue({})
.compile(); .compile();
service = module.get<HistoryService>(HistoryService); service = module.get<HistoryService>(HistoryService);
@ -99,7 +105,7 @@ describe('HistoryService', () => {
describe('createOrUpdateHistoryEntry', () => { describe('createOrUpdateHistoryEntry', () => {
describe('works', () => { describe('works', () => {
it('without an preexisting entry', async () => { it('without an preexisting entry', async () => {
const user = new User(); const user = {} as User;
const alias = 'alias'; const alias = 'alias';
jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined); jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined);
jest jest
@ -118,7 +124,7 @@ describe('HistoryService', () => {
}); });
it('with an preexisting entry', async () => { it('with an preexisting entry', async () => {
const user = new User(); const user = {} as User;
const alias = 'alias'; const alias = 'alias';
const historyEntry = HistoryEntry.create( const historyEntry = HistoryEntry.create(
user, user,
@ -148,7 +154,7 @@ describe('HistoryService', () => {
describe('updateHistoryEntry', () => { describe('updateHistoryEntry', () => {
describe('works', () => { describe('works', () => {
it('with an entry', async () => { it('with an entry', async () => {
const user = new User(); const user = {} as User;
const alias = 'alias'; const alias = 'alias';
const note = Note.create(user, alias); const note = Note.create(user, alias);
const historyEntry = HistoryEntry.create(user, note); const historyEntry = HistoryEntry.create(user, note);
@ -173,7 +179,7 @@ describe('HistoryService', () => {
}); });
it('without an entry', async () => { it('without an entry', async () => {
const user = new User(); const user = {} as User;
const alias = 'alias'; const alias = 'alias';
const note = Note.create(user, alias); const note = Note.create(user, alias);
jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined); jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined);
@ -192,7 +198,7 @@ describe('HistoryService', () => {
describe('deleteHistoryEntry', () => { describe('deleteHistoryEntry', () => {
describe('works', () => { describe('works', () => {
it('with an entry', async () => { it('with an entry', async () => {
const user = new User(); const user = {} as User;
const alias = 'alias'; const alias = 'alias';
const note = Note.create(user, alias); const note = Note.create(user, alias);
const historyEntry = HistoryEntry.create(user, note); const historyEntry = HistoryEntry.create(user, note);
@ -208,7 +214,7 @@ describe('HistoryService', () => {
}); });
it('without an entry', async () => { it('without an entry', async () => {
const user = new User(); const user = {} as User;
const alias = 'alias'; const alias = 'alias';
const note = Note.create(user, alias); const note = Note.create(user, alias);
jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined); jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined);
@ -225,7 +231,7 @@ describe('HistoryService', () => {
describe('toHistoryEntryDto', () => { describe('toHistoryEntryDto', () => {
describe('works', () => { describe('works', () => {
it('with aliased note', async () => { it('with aliased note', async () => {
const user = new User(); const user = {} as User;
const alias = 'alias'; const alias = 'alias';
const title = 'title'; const title = 'title';
const tags = ['tag1', 'tag2']; const tags = ['tag1', 'tag2'];
@ -247,7 +253,7 @@ describe('HistoryService', () => {
}); });
it('with regular note', async () => { it('with regular note', async () => {
const user = new User(); const user = {} as User;
const title = 'title'; const title = 'title';
const id = 'id'; const id = 'id';
const tags = ['tag1', 'tag2']; const tags = ['tag1', 'tag2'];

View file

@ -25,6 +25,8 @@ import { MediaService } from './media.service';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { ClientError, NotInDBError, PermissionError } from '../errors/errors'; import { ClientError, NotInDBError, PermissionError } from '../errors/errors';
import { NoteGroupPermission } from '../permissions/note-group-permission.entity';
import { NoteUserPermission } from '../permissions/note-user-permission.entity';
describe('MediaService', () => { describe('MediaService', () => {
let service: MediaService; let service: MediaService;
@ -70,6 +72,10 @@ describe('MediaService', () => {
.useClass(Repository) .useClass(Repository)
.overrideProvider(getRepositoryToken(Tag)) .overrideProvider(getRepositoryToken(Tag))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(NoteGroupPermission))
.useValue({})
.overrideProvider(getRepositoryToken(NoteUserPermission))
.useValue({})
.overrideProvider(getRepositoryToken(MediaUpload)) .overrideProvider(getRepositoryToken(MediaUpload))
.useClass(Repository) .useClass(Repository)
.compile(); .compile();

View file

@ -13,10 +13,18 @@ import { AuthorColor } from './author-color.entity';
import { Note } from './note.entity'; import { Note } from './note.entity';
import { NotesService } from './notes.service'; import { NotesService } from './notes.service';
import { Tag } from './tag.entity'; import { Tag } from './tag.entity';
import { NoteGroupPermission } from '../permissions/note-group-permission.entity';
import { NoteUserPermission } from '../permissions/note-user-permission.entity';
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([Note, AuthorColor, Tag]), TypeOrmModule.forFeature([
Note,
AuthorColor,
Tag,
NoteGroupPermission,
NoteUserPermission,
]),
forwardRef(() => RevisionsModule), forwardRef(() => RevisionsModule),
UsersModule, UsersModule,
LoggerModule, LoggerModule,

View file

@ -18,6 +18,8 @@ import { AuthorColor } from './author-color.entity';
import { Note } from './note.entity'; import { Note } from './note.entity';
import { NotesService } from './notes.service'; import { NotesService } from './notes.service';
import { Tag } from './tag.entity'; import { Tag } from './tag.entity';
import { NoteGroupPermission } from '../permissions/note-group-permission.entity';
import { NoteUserPermission } from '../permissions/note-user-permission.entity';
describe('NotesService', () => { describe('NotesService', () => {
let service: NotesService; let service: NotesService;
@ -53,6 +55,10 @@ describe('NotesService', () => {
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(Tag)) .overrideProvider(getRepositoryToken(Tag))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(NoteGroupPermission))
.useValue({})
.overrideProvider(getRepositoryToken(NoteUserPermission))
.useValue({})
.compile(); .compile();
service = module.get<NotesService>(NotesService); service = module.get<NotesService>(NotesService);
}); });

View file

@ -159,6 +159,7 @@ export class NotesService {
historyEntries: [], historyEntries: [],
updatedAt: new Date(), updatedAt: new Date(),
userName: 'Testy', userName: 'Testy',
groups: [],
}, },
description: 'Very descriptive text.', description: 'Very descriptive text.',
userPermissions: [], userPermissions: [],

View file

@ -17,6 +17,8 @@ import { Authorship } from './authorship.entity';
import { Revision } from './revision.entity'; import { Revision } from './revision.entity';
import { RevisionsService } from './revisions.service'; import { RevisionsService } from './revisions.service';
import { Tag } from '../notes/tag.entity'; import { Tag } from '../notes/tag.entity';
import { NoteGroupPermission } from '../permissions/note-group-permission.entity';
import { NoteUserPermission } from '../permissions/note-user-permission.entity';
describe('RevisionsService', () => { describe('RevisionsService', () => {
let service: RevisionsService; let service: RevisionsService;
@ -48,6 +50,10 @@ describe('RevisionsService', () => {
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(Tag)) .overrideProvider(getRepositoryToken(Tag))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(NoteGroupPermission))
.useValue({})
.overrideProvider(getRepositoryToken(NoteUserPermission))
.useValue({})
.compile(); .compile();
service = module.get<RevisionsService>(RevisionsService); service = module.get<RevisionsService>(RevisionsService);

View file

@ -7,6 +7,7 @@
import { import {
CreateDateColumn, CreateDateColumn,
Entity, Entity,
ManyToMany,
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
UpdateDateColumn, UpdateDateColumn,
} from 'typeorm'; } from 'typeorm';
@ -14,6 +15,7 @@ import { Column, OneToMany } from 'typeorm';
import { Note } from '../notes/note.entity'; import { Note } from '../notes/note.entity';
import { AuthToken } from '../auth/auth-token.entity'; import { AuthToken } from '../auth/auth-token.entity';
import { Identity } from './identity.entity'; import { Identity } from './identity.entity';
import { Group } from '../groups/group.entity';
import { HistoryEntry } from '../history/history-entry.entity'; import { HistoryEntry } from '../history/history-entry.entity';
@Entity() @Entity()
@ -52,9 +54,15 @@ export class User {
@OneToMany((_) => Identity, (identity) => identity.user) @OneToMany((_) => Identity, (identity) => identity.user)
identities: Identity[]; identities: Identity[];
@ManyToMany((_) => Group, (group) => group.members)
groups: Group[];
@OneToMany((_) => HistoryEntry, (historyEntry) => historyEntry.user) @OneToMany((_) => HistoryEntry, (historyEntry) => historyEntry.user)
historyEntries: HistoryEntry[]; historyEntries: HistoryEntry[];
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
public static create( public static create(
userName: string, userName: string,
displayName: string, displayName: string,