Merge pull request #1344 from hedgedoc/feature/rename_authorship_edit

This commit is contained in:
David Mehren 2021-05-31 22:42:21 +02:00 committed by GitHub
commit 3bfde06007
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 77 additions and 81 deletions

View file

@ -21,7 +21,7 @@ import { User } from '../../../../users/user.entity';
import { Note } from '../../../../notes/note.entity';
import { AuthToken } from '../../../../auth/auth-token.entity';
import { Identity } from '../../../../users/identity.entity';
import { Authorship } from '../../../../revisions/authorship.entity';
import { Edit } from '../../../../revisions/edit.entity';
import { Revision } from '../../../../revisions/revision.entity';
import { Tag } from '../../../../notes/tag.entity';
import { HistoryEntry } from '../../../../history/history-entry.entity';
@ -59,7 +59,7 @@ describe('HistoryController', () => {
.useValue({})
.overrideProvider(getRepositoryToken(Identity))
.useValue({})
.overrideProvider(getRepositoryToken(Authorship))
.overrideProvider(getRepositoryToken(Edit))
.useValue({})
.overrideProvider(getRepositoryToken(Revision))
.useValue({})

View file

@ -16,7 +16,7 @@ import { Identity } from '../../../users/identity.entity';
import { MediaModule } from '../../../media/media.module';
import { NoteGroupPermission } from '../../../permissions/note-group-permission.entity';
import { NoteUserPermission } from '../../../permissions/note-user-permission.entity';
import { Authorship } from '../../../revisions/authorship.entity';
import { Edit } from '../../../revisions/edit.entity';
import { ConfigModule } from '@nestjs/config';
import appConfigMock from '../../../config/mock/app.config.mock';
import authConfigMock from '../../../config/mock/auth.config.mock';
@ -67,7 +67,7 @@ describe('MeController', () => {
.useValue({})
.overrideProvider(getRepositoryToken(NoteUserPermission))
.useValue({})
.overrideProvider(getRepositoryToken(Authorship))
.overrideProvider(getRepositoryToken(Edit))
.useValue({})
.overrideProvider(getRepositoryToken(MediaUpload))
.useValue({})

View file

@ -19,7 +19,7 @@ import externalConfigMock from '../../../config/mock/external-services.config.mo
import { MediaModule } from '../../../media/media.module';
import { NotesModule } from '../../../notes/notes.module';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Authorship } from '../../../revisions/authorship.entity';
import { Edit } from '../../../revisions/edit.entity';
import { AuthToken } from '../../../auth/auth-token.entity';
import { Identity } from '../../../users/identity.entity';
import { MediaUpload } from '../../../media/media-upload.entity';
@ -54,7 +54,7 @@ describe('MediaController', () => {
],
controllers: [MediaController],
})
.overrideProvider(getRepositoryToken(Authorship))
.overrideProvider(getRepositoryToken(Edit))
.useValue({})
.overrideProvider(getRepositoryToken(AuthToken))
.useValue({})

View file

@ -27,7 +27,7 @@ import { ConfigModule } from '@nestjs/config';
import appConfigMock from '../../../config/mock/app.config.mock';
import mediaConfigMock from '../../../config/mock/media.config.mock';
import { Revision } from '../../../revisions/revision.entity';
import { Authorship } from '../../../revisions/authorship.entity';
import { Edit } from '../../../revisions/edit.entity';
import { User } from '../../../users/user.entity';
import { AuthToken } from '../../../auth/auth-token.entity';
import { Identity } from '../../../users/identity.entity';
@ -77,7 +77,7 @@ describe('NotesController', () => {
.useValue({})
.overrideProvider(getRepositoryToken(Revision))
.useValue({})
.overrideProvider(getRepositoryToken(Authorship))
.overrideProvider(getRepositoryToken(Edit))
.useValue({})
.overrideProvider(getRepositoryToken(User))
.useValue({})

View file

@ -16,7 +16,7 @@ import { LoggerModule } from '../../../logger/logger.module';
import { Note } from '../../../notes/note.entity';
import { NotesModule } from '../../../notes/notes.module';
import { Tag } from '../../../notes/tag.entity';
import { Authorship } from '../../../revisions/authorship.entity';
import { Edit } from '../../../revisions/edit.entity';
import { Revision } from '../../../revisions/revision.entity';
import { AuthToken } from '../../../auth/auth-token.entity';
import { Identity } from '../../../users/identity.entity';
@ -63,7 +63,7 @@ describe('Me Controller', () => {
.useValue({})
.overrideProvider(getRepositoryToken(Identity))
.useValue({})
.overrideProvider(getRepositoryToken(Authorship))
.overrideProvider(getRepositoryToken(Edit))
.useValue({})
.overrideProvider(getRepositoryToken(Revision))
.useValue({})

View file

@ -16,7 +16,7 @@ import { MediaModule } from '../../../media/media.module';
import { Note } from '../../../notes/note.entity';
import { NotesModule } from '../../../notes/notes.module';
import { Tag } from '../../../notes/tag.entity';
import { Authorship } from '../../../revisions/authorship.entity';
import { Edit } from '../../../revisions/edit.entity';
import { Revision } from '../../../revisions/revision.entity';
import { AuthToken } from '../../../auth/auth-token.entity';
import { Identity } from '../../../users/identity.entity';
@ -43,7 +43,7 @@ describe('Media Controller', () => {
NotesModule,
],
})
.overrideProvider(getRepositoryToken(Authorship))
.overrideProvider(getRepositoryToken(Edit))
.useValue({})
.overrideProvider(getRepositoryToken(AuthToken))
.useValue({})

View file

@ -15,7 +15,7 @@ import { LoggerModule } from '../../../logger/logger.module';
import { Note } from '../../../notes/note.entity';
import { NotesService } from '../../../notes/notes.service';
import { Tag } from '../../../notes/tag.entity';
import { Authorship } from '../../../revisions/authorship.entity';
import { Edit } from '../../../revisions/edit.entity';
import { Revision } from '../../../revisions/revision.entity';
import { RevisionsModule } from '../../../revisions/revisions.module';
import { AuthToken } from '../../../auth/auth-token.entity';
@ -79,7 +79,7 @@ describe('Notes Controller', () => {
.useValue({})
.overrideProvider(getRepositoryToken(Revision))
.useValue({})
.overrideProvider(getRepositoryToken(Authorship))
.overrideProvider(getRepositoryToken(Edit))
.useValue({})
.overrideProvider(getRepositoryToken(User))
.useValue({})

View file

@ -11,7 +11,7 @@ import {
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Authorship } from '../revisions/authorship.entity';
import { Edit } from '../revisions/edit.entity';
import { Session } from '../users/session.entity';
import { User } from '../users/user.entity';
@ -20,7 +20,7 @@ export type AuthorColor = number;
/**
* The author represents a single user editing a note.
* A 'user' can either be a registered and logged-in user or a browser session identified by its cookie.
* All edits (aka authorships) of one user in a note must belong to the same author, so that the same color can be displayed.
* All edits of one user in a note must belong to the same author, so that the same color can be displayed.
*/
@Entity()
export class Author {
@ -49,23 +49,23 @@ export class Author {
user: User | null;
/**
* List of authorships that this author created
* All authorships must belong to the same note
* List of edits that this author created
* All edits must belong to the same note
*/
@OneToMany(() => Authorship, (authorship) => authorship.author)
authorships: Authorship[];
@OneToMany(() => Edit, (edit) => edit.author)
edits: Edit[];
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
public static create(
color: number,
): Pick<Author, 'color' | 'sessions' | 'user' | 'authorships'> {
): Pick<Author, 'color' | 'sessions' | 'user' | 'edits'> {
const newAuthor = new Author();
newAuthor.color = color;
newAuthor.sessions = [];
newAuthor.user = null;
newAuthor.authorships = [];
newAuthor.edits = [];
return newAuthor;
}
}

View file

@ -14,7 +14,7 @@ import { NotesModule } from '../notes/notes.module';
import { getConnectionToken, getRepositoryToken } from '@nestjs/typeorm';
import { Identity } from '../users/identity.entity';
import { User } from '../users/user.entity';
import { Authorship } from '../revisions/authorship.entity';
import { Edit } from '../revisions/edit.entity';
import { HistoryEntry } from './history-entry.entity';
import { Note } from '../notes/note.entity';
import { Tag } from '../notes/tag.entity';
@ -74,7 +74,7 @@ describe('HistoryService', () => {
.useValue({})
.overrideProvider(getRepositoryToken(Identity))
.useValue({})
.overrideProvider(getRepositoryToken(Authorship))
.overrideProvider(getRepositoryToken(Edit))
.useValue({})
.overrideProvider(getRepositoryToken(Revision))
.useValue({})

View file

@ -13,7 +13,7 @@ import { LoggerModule } from '../logger/logger.module';
import { Note } from '../notes/note.entity';
import { NotesModule } from '../notes/notes.module';
import { Tag } from '../notes/tag.entity';
import { Authorship } from '../revisions/authorship.entity';
import { Edit } from '../revisions/edit.entity';
import { Revision } from '../revisions/revision.entity';
import { AuthToken } from '../auth/auth-token.entity';
import { Identity } from '../users/identity.entity';
@ -57,7 +57,7 @@ describe('MediaService', () => {
UsersModule,
],
})
.overrideProvider(getRepositoryToken(Authorship))
.overrideProvider(getRepositoryToken(Edit))
.useValue({})
.overrideProvider(getRepositoryToken(AuthToken))
.useValue({})

View file

@ -5,7 +5,7 @@
*/
import { IsArray, IsString, ValidateNested } from 'class-validator';
import { NoteAuthorshipDto } from './note-authorship.dto';
import { EditDto } from '../revisions/edit.dto';
import { NoteMetadataDto } from './note-metadata.dto';
import { ApiProperty } from '@nestjs/swagger';
@ -26,10 +26,10 @@ export class NoteDto {
metadata: NoteMetadataDto;
/**
* Authorship information of this note
* Edit information of this note
*/
@IsArray()
@ValidateNested({ each: true })
@ApiProperty({ isArray: true, type: NoteAuthorshipDto })
editedByAtPosition: NoteAuthorshipDto[];
@ApiProperty({ isArray: true, type: EditDto })
editedByAtPosition: EditDto[];
}

View file

@ -8,7 +8,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Author } from '../authors/author.entity';
import { LoggerModule } from '../logger/logger.module';
import { Authorship } from '../revisions/authorship.entity';
import { Edit } from '../revisions/edit.entity';
import { Revision } from '../revisions/revision.entity';
import { RevisionsModule } from '../revisions/revisions.module';
import { AuthToken } from '../auth/auth-token.entity';
@ -89,7 +89,7 @@ describe('NotesService', () => {
.useValue({})
.overrideProvider(getRepositoryToken(Identity))
.useValue({})
.overrideProvider(getRepositoryToken(Authorship))
.overrideProvider(getRepositoryToken(Edit))
.useValue({})
.overrideProvider(getRepositoryToken(Revision))
.useClass(Repository)
@ -680,21 +680,21 @@ describe('NotesService', () => {
.mockImplementation(async (note: Note): Promise<Note> => note);
const note = await service.createNote(content);
const revisions = await note.revisions;
revisions[0].authorships = [
revisions[0].edits = [
{
revisions: revisions,
startPos: 0,
endPos: 1,
updatedAt: new Date(1549312452000),
author: author,
} as Authorship,
} as Edit,
{
revisions: revisions,
startPos: 0,
endPos: 1,
updatedAt: new Date(1549312452001),
author: author,
} as Authorship,
} as Edit,
];
revisions[0].createdAt = new Date(1549312452000);
jest.spyOn(revisionRepo, 'findOne').mockResolvedValue(revisions[0]);
@ -776,21 +776,21 @@ describe('NotesService', () => {
.mockImplementation(async (note: Note): Promise<Note> => note);
const note = await service.createNote(content);
const revisions = await note.revisions;
revisions[0].authorships = [
revisions[0].edits = [
{
revisions: revisions,
startPos: 0,
endPos: 1,
updatedAt: new Date(1549312452000),
author: author,
} as Authorship,
} as Edit,
{
revisions: revisions,
startPos: 0,
endPos: 1,
updatedAt: new Date(1549312452001),
author: author,
} as Authorship,
} as Edit,
];
revisions[0].createdAt = new Date(1549312452000);
jest

View file

@ -198,8 +198,8 @@ export class NotesService {
return await this.userRepository
.createQueryBuilder('user')
.innerJoin('user.authors', 'author')
.innerJoin('author.authorships', 'authorship')
.innerJoin('authorship.revisions', 'revision')
.innerJoin('author.edits', 'edit')
.innerJoin('edit.revisions', 'revision')
.innerJoin('revision.note', 'note')
.where('note.id = :id', { id: note.id })
.getMany();
@ -322,14 +322,14 @@ export class NotesService {
*/
async calculateUpdateUser(note: Note): Promise<User | null> {
const lastRevision = await this.getLatestRevision(note);
if (lastRevision && lastRevision.authorships) {
// Sort the last Revisions Authorships by their updatedAt Date to get the latest one
// the user of that Authorship is the updateUser
return lastRevision.authorships.sort(
if (lastRevision && lastRevision.edits) {
// Sort the last Revisions Edits by their updatedAt Date to get the latest one
// the user of that Edit is the updateUser
return lastRevision.edits.sort(
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime(),
)[0].author.user;
}
// If there are no Authorships, the owner is the updateUser
// If there are no Edits, the owner is the updateUser
return note.owner;
}

View file

@ -13,7 +13,7 @@ import { LoggerModule } from '../logger/logger.module';
import { Note } from '../notes/note.entity';
import { NotesModule } from '../notes/notes.module';
import { Tag } from '../notes/tag.entity';
import { Authorship } from '../revisions/authorship.entity';
import { Edit } from '../revisions/edit.entity';
import { Revision } from '../revisions/revision.entity';
import { Identity } from '../users/identity.entity';
import { Session } from '../users/session.entity';
@ -49,7 +49,7 @@ describe('PermissionsService', () => {
.useValue({})
.overrideProvider(getRepositoryToken(Identity))
.useValue({})
.overrideProvider(getRepositoryToken(Authorship))
.overrideProvider(getRepositoryToken(Edit))
.useValue({})
.overrideProvider(getRepositoryToken(Revision))
.useValue({})

View file

@ -4,18 +4,20 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { IsDate, IsNumber, IsString, Min } from 'class-validator';
import { IsDate, IsNumber, IsOptional, IsString, Min } from 'class-validator';
import { UserInfoDto } from '../users/user-info.dto';
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class NoteAuthorshipDto {
export class EditDto {
/**
* Username of the user who authored this section
* Is `null` if the user is anonymous
* @example "john.smith"
*/
@IsString()
@ApiProperty()
userName: UserInfoDto['userName'];
@IsOptional()
@ApiPropertyOptional()
userName: UserInfoDto['userName'] | null;
/**
* Character index of the start of this section

View file

@ -17,23 +17,23 @@ import { Author } from '../authors/author.entity';
import { Revision } from './revision.entity';
/**
* The Authorship represents a change in the content of a note by a particular {@link Author}
* The Edit represents a change in the content of a note by a particular {@link Author}
*/
@Entity()
export class Authorship {
export class Edit {
@PrimaryGeneratedColumn('uuid')
id: string;
/**
* Revisions this authorship appears in
* Revisions this edit appears in
*/
@ManyToMany((_) => Revision, (revision) => revision.authorships)
@ManyToMany((_) => Revision, (revision) => revision.edits)
revisions: Revision[];
/**
* Author that created the change
*/
@ManyToOne(() => Author, (author) => author.authorships)
@ManyToOne(() => Author, (author) => author.edits)
author: Author;
@Column()
@ -52,10 +52,10 @@ export class Authorship {
private constructor() {}
public static create(author: Author, startPos: number, endPos: number) {
const newAuthorship = new Authorship();
newAuthorship.author = author;
newAuthorship.startPos = startPos;
newAuthorship.endPos = endPos;
return newAuthorship;
const newEdit = new Edit();
newEdit.author = author;
newEdit.startPos = startPos;
newEdit.endPos = endPos;
return newEdit;
}
}

View file

@ -13,7 +13,7 @@ import {
} from 'typeorm';
import { JoinTable, ManyToMany } from 'typeorm';
import { Note } from '../notes/note.entity';
import { Authorship } from './authorship.entity';
import { Edit } from './edit.entity';
/**
* The state of a note at a particular point in time,
@ -59,11 +59,11 @@ export class Revision {
@ManyToOne((_) => Note, (note) => note.revisions, { onDelete: 'CASCADE' })
note: Note;
/**
* All authorship objects which are used in the revision.
* All edit objects which are used in the revision.
*/
@ManyToMany((_) => Authorship, (authorship) => authorship.revisions)
@ManyToMany((_) => Edit, (edit) => edit.revisions)
@JoinTable()
authorships: Authorship[];
edits: Edit[];
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}

View file

@ -9,14 +9,14 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthorsModule } from '../authors/authors.module';
import { LoggerModule } from '../logger/logger.module';
import { NotesModule } from '../notes/notes.module';
import { Authorship } from './authorship.entity';
import { Edit } from './edit.entity';
import { Revision } from './revision.entity';
import { RevisionsService } from './revisions.service';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
TypeOrmModule.forFeature([Revision, Authorship]),
TypeOrmModule.forFeature([Revision, Edit]),
forwardRef(() => NotesModule),
LoggerModule,
ConfigModule,

View file

@ -17,7 +17,7 @@ import { AuthToken } from '../auth/auth-token.entity';
import { Identity } from '../users/identity.entity';
import { Session } from '../users/session.entity';
import { User } from '../users/user.entity';
import { Authorship } from './authorship.entity';
import { Edit } from './edit.entity';
import { Revision } from './revision.entity';
import { RevisionsService } from './revisions.service';
import { Tag } from '../notes/tag.entity';
@ -49,7 +49,7 @@ describe('RevisionsService', () => {
}),
],
})
.overrideProvider(getRepositoryToken(Authorship))
.overrideProvider(getRepositoryToken(Edit))
.useValue({})
.overrideProvider(getRepositoryToken(User))
.useValue({})

View file

@ -10,7 +10,7 @@ import { Session } from './users/session.entity';
import { User } from './users/user.entity';
import { Note } from './notes/note.entity';
import { Revision } from './revisions/revision.entity';
import { Authorship } from './revisions/authorship.entity';
import { Edit } from './revisions/edit.entity';
import { NoteGroupPermission } from './permissions/note-group-permission.entity';
import { NoteUserPermission } from './permissions/note-user-permission.entity';
import { Group } from './groups/group.entity';
@ -30,7 +30,7 @@ createConnection({
User,
Note,
Revision,
Authorship,
Edit,
NoteGroupPermission,
NoteUserPermission,
Group,
@ -64,19 +64,13 @@ createConnection({
'This is a test note',
'This is a test note',
);
const authorship = Authorship.create(author, 1, 42);
revision.authorships = [authorship];
const edit = Edit.create(author, 1, 42);
revision.edits = [edit];
notes[i].revisions = Promise.all([revision]);
notes[i].userPermissions = [];
notes[i].groupPermissions = [];
user.ownedNotes = [notes[i]];
await connection.manager.save([
notes[i],
user,
revision,
authorship,
author,
]);
await connection.manager.save([notes[i], user, revision, edit, author]);
}
const foundUser = await connection.manager.findOne(User);
if (!foundUser) {