mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-23 10:16:32 -05:00
Merge pull request #514 from codimd/note-metadata-in-db
Save note metadata in the database
This commit is contained in:
commit
135e7ddfc2
16 changed files with 143 additions and 60 deletions
|
@ -1,17 +1,16 @@
|
|||
@startuml
|
||||
' hide the spot
|
||||
hide circle
|
||||
|
||||
' avoid problems with angled crows feet
|
||||
skinparam linetype ortho
|
||||
skinparam nodesep 60
|
||||
|
||||
entity "Note" {
|
||||
*id : uuid <<generated>>
|
||||
--
|
||||
*shortid : text
|
||||
*alias : text
|
||||
alias : text
|
||||
*viewcount : number
|
||||
*ownerId : uuid <<FK User>>
|
||||
description: text
|
||||
title: text
|
||||
}
|
||||
|
||||
entity "User" {
|
||||
|
@ -49,7 +48,7 @@ entity "Identity" {
|
|||
passwordHash : text
|
||||
}
|
||||
|
||||
entity "Session" as seesion {
|
||||
entity "Session" {
|
||||
*id : text
|
||||
--
|
||||
*expiredAt : number
|
||||
|
@ -108,13 +107,18 @@ entity "Group" {
|
|||
*special : boolean
|
||||
}
|
||||
|
||||
entity "NoteGroupPermission" {
|
||||
entity "NoteGroupPermission" {
|
||||
*groupId : number <<FK Group>>
|
||||
*noteId : uuid <<FK Note>>
|
||||
--
|
||||
*canEdit : boolean
|
||||
}
|
||||
|
||||
entity "Tag" {
|
||||
*id: number <<generated>>
|
||||
*name: text
|
||||
}
|
||||
|
||||
entity "MediaUpload" {
|
||||
*id : text <<unique>>
|
||||
--
|
||||
|
@ -125,20 +129,26 @@ entity "MediaUpload" {
|
|||
*createdAt : date
|
||||
}
|
||||
|
||||
Note "1" - "1..*" Revision
|
||||
User "1" -- "0..*" Note: owner
|
||||
User "1" -u- "1..*" Identity
|
||||
User "1" - "1..*" authToken
|
||||
User "1" -l- "1..*" Session
|
||||
User "1" - "0..*" MediaUpload
|
||||
User "0..*" -- "0..*" Note
|
||||
User "1" - "0..*" Authorship
|
||||
|
||||
(User, Note) . AuthorColors
|
||||
|
||||
Revision "0..*" - "0..*" Authorship
|
||||
(Revision, Authorship) .. RevisionAuthorship
|
||||
Authorship "0..*" -- "1" User
|
||||
Note "0..*" -- "1" User : owner
|
||||
Note "1" -- "0..*" NoteUserPermission
|
||||
NoteUserPermission "1" -- "1" User
|
||||
Note "1" -- "0..*" NoteGroupPermission
|
||||
NoteGroupPermission "0..*" -- "1" Group
|
||||
Identity "1..*" -- "1" User
|
||||
authToken "1..*" -- "1" User
|
||||
seesion "1..*" -- "1" User
|
||||
Note "0..*" -- "0..*" User : color
|
||||
(Note, User) .. AuthorColors
|
||||
MediaUpload "0..*" -- "1" Note
|
||||
MediaUpload "0..*" -- "1" User
|
||||
|
||||
MediaUpload "0..*" -- "1" Note
|
||||
Note "1" - "1..*" Revision
|
||||
Note "0..*" -l- "0..*" Tag
|
||||
Note "0..*" -- "0..*" Group
|
||||
|
||||
User "0..*" -- "0..*" Note
|
||||
(User, Note) . NoteUserPermission
|
||||
(Note, Group) . NoteGroupPermission
|
||||
|
||||
@enduml
|
||||
|
|
|
@ -5,6 +5,7 @@ import { LoggerModule } from '../../../logger/logger.module';
|
|||
import { AuthorColor } from '../../../notes/author-color.entity';
|
||||
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 { Revision } from '../../../revisions/revision.entity';
|
||||
import { AuthToken } from '../../../users/auth-token.entity';
|
||||
|
@ -35,6 +36,8 @@ describe('Me Controller', () => {
|
|||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Revision))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Tag))
|
||||
.useValue({})
|
||||
.compile();
|
||||
|
||||
controller = module.get<MeController>(MeController);
|
||||
|
|
|
@ -6,6 +6,7 @@ import { MediaModule } from '../../../media/media.module';
|
|||
import { AuthorColor } from '../../../notes/author-color.entity';
|
||||
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 { Revision } from '../../../revisions/revision.entity';
|
||||
import { AuthToken } from '../../../users/auth-token.entity';
|
||||
|
@ -37,6 +38,8 @@ describe('Media Controller', () => {
|
|||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(User))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Tag))
|
||||
.useValue({})
|
||||
.compile();
|
||||
|
||||
controller = module.get<MediaController>(MediaController);
|
||||
|
|
|
@ -4,6 +4,7 @@ import { LoggerModule } from '../../../logger/logger.module';
|
|||
import { AuthorColor } from '../../../notes/author-color.entity';
|
||||
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 { Revision } from '../../../revisions/revision.entity';
|
||||
import { RevisionsModule } from '../../../revisions/revisions.module';
|
||||
|
@ -25,6 +26,10 @@ describe('Notes Controller', () => {
|
|||
provide: getRepositoryToken(Note),
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(Tag),
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
imports: [RevisionsModule, UsersModule, LoggerModule],
|
||||
})
|
||||
|
@ -44,6 +49,8 @@ describe('Notes Controller', () => {
|
|||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Note))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Tag))
|
||||
.useValue({})
|
||||
.compile();
|
||||
|
||||
controller = module.get<NotesController>(NotesController);
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
Put,
|
||||
} from '@nestjs/common';
|
||||
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
|
||||
import { NoteMetadataUpdateDto } from '../../../notes/note-metadata.dto';
|
||||
import { NotePermissionsUpdateDto } from '../../../notes/note-permissions.dto';
|
||||
import { NotesService } from '../../../notes/notes.service';
|
||||
import { RevisionsService } from '../../../revisions/revisions.service';
|
||||
|
@ -21,7 +22,7 @@ export class NotesController {
|
|||
private noteService: NotesService,
|
||||
private revisionsService: RevisionsService,
|
||||
) {
|
||||
this.logger.setContext(NotesController.name);
|
||||
this.logger.setContext(NotesController.name);
|
||||
}
|
||||
|
||||
@Post()
|
||||
|
|
|
@ -4,6 +4,7 @@ import { LoggerModule } from '../logger/logger.module';
|
|||
import { AuthorColor } from '../notes/author-color.entity';
|
||||
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 { Revision } from '../revisions/revision.entity';
|
||||
import { AuthToken } from '../users/auth-token.entity';
|
||||
|
@ -43,6 +44,8 @@ describe('MediaService', () => {
|
|||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(User))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Tag))
|
||||
.useValue({})
|
||||
.compile();
|
||||
|
||||
service = module.get<MediaService>(MediaService);
|
||||
|
|
|
@ -34,3 +34,13 @@ export class NoteMetadataDto {
|
|||
@ValidateNested()
|
||||
permissions: NotePermissionsDto;
|
||||
}
|
||||
|
||||
export class NoteMetadataUpdateDto {
|
||||
@IsString()
|
||||
title: string;
|
||||
@IsString()
|
||||
description: string;
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
tags: string[];
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ import { generate as shortIdGenerate } from 'shortid';
|
|||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
|
@ -11,6 +13,7 @@ import { NoteUserPermission } from '../permissions/note-user-permission.entity';
|
|||
import { Revision } from '../revisions/revision.entity';
|
||||
import { User } from '../users/user.entity';
|
||||
import { AuthorColor } from './author-color.entity';
|
||||
import { Tag } from './tag.entity';
|
||||
|
||||
@Entity('Notes')
|
||||
export class Note {
|
||||
|
@ -25,7 +28,7 @@ export class Note {
|
|||
unique: true,
|
||||
nullable: true,
|
||||
})
|
||||
alias: string;
|
||||
alias?: string;
|
||||
@OneToMany(
|
||||
_ => NoteGroupPermission,
|
||||
groupPermission => groupPermission.note,
|
||||
|
@ -59,10 +62,27 @@ export class Note {
|
|||
)
|
||||
authorColors: AuthorColor[];
|
||||
|
||||
@Column({
|
||||
nullable: true,
|
||||
})
|
||||
description?: string;
|
||||
@Column({
|
||||
nullable: true,
|
||||
})
|
||||
title?: string;
|
||||
|
||||
@ManyToMany(
|
||||
_ => Tag,
|
||||
tag => tag.notes,
|
||||
{ eager: true, cascade: true },
|
||||
)
|
||||
@JoinTable()
|
||||
tags: Tag[];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
private constructor() {}
|
||||
|
||||
public static create(owner?: User, alias?: string, shortid?: string) {
|
||||
public static create(owner?: User, alias?: string, shortid?: string): Note {
|
||||
if (!shortid) {
|
||||
shortid = shortIdGenerate();
|
||||
}
|
||||
|
@ -74,6 +94,10 @@ export class Note {
|
|||
newNote.authorColors = [];
|
||||
newNote.userPermissions = [];
|
||||
newNote.groupPermissions = [];
|
||||
newNote.revisions = Promise.resolve([]);
|
||||
newNote.description = null;
|
||||
newNote.title = null;
|
||||
newNote.tags = [];
|
||||
return newNote;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
import { Note } from './note.entity';
|
||||
|
||||
export class NoteUtils {
|
||||
public static parseTitle(note: Note): string {
|
||||
// TODO: Implement method
|
||||
return 'Hardcoded note title';
|
||||
}
|
||||
|
||||
public static parseDescription(note: Note): string {
|
||||
// TODO: Implement method
|
||||
return 'Hardcoded note description';
|
||||
}
|
||||
|
||||
public static parseTags(note: Note): string[] {
|
||||
// TODO: Implement method
|
||||
return ['Hardcoded note tag'];
|
||||
}
|
||||
}
|
|
@ -6,10 +6,11 @@ import { UsersModule } from '../users/users.module';
|
|||
import { AuthorColor } from './author-color.entity';
|
||||
import { Note } from './note.entity';
|
||||
import { NotesService } from './notes.service';
|
||||
import { Tag } from './tag.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Note, AuthorColor]),
|
||||
TypeOrmModule.forFeature([Note, AuthorColor, Tag]),
|
||||
forwardRef(() => RevisionsModule),
|
||||
UsersModule,
|
||||
LoggerModule,
|
||||
|
|
|
@ -11,6 +11,7 @@ import { UsersModule } from '../users/users.module';
|
|||
import { AuthorColor } from './author-color.entity';
|
||||
import { Note } from './note.entity';
|
||||
import { NotesService } from './notes.service';
|
||||
import { Tag } from './tag.entity';
|
||||
|
||||
describe('NotesService', () => {
|
||||
let service: NotesService;
|
||||
|
@ -23,6 +24,10 @@ describe('NotesService', () => {
|
|||
provide: getRepositoryToken(Note),
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(Tag),
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
imports: [UsersModule, RevisionsModule, LoggerModule],
|
||||
})
|
||||
|
@ -40,6 +45,8 @@ describe('NotesService', () => {
|
|||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Note))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Tag))
|
||||
.useValue({})
|
||||
.compile();
|
||||
service = module.get<NotesService>(NotesService);
|
||||
});
|
||||
|
|
|
@ -7,20 +7,21 @@ import { Revision } from '../revisions/revision.entity';
|
|||
import { RevisionsService } from '../revisions/revisions.service';
|
||||
import { User } from '../users/user.entity';
|
||||
import { UsersService } from '../users/users.service';
|
||||
import { NoteMetadataDto } from './note-metadata.dto';
|
||||
import { NoteMetadataDto, NoteMetadataUpdateDto } from './note-metadata.dto';
|
||||
import {
|
||||
NotePermissionsDto,
|
||||
NotePermissionsUpdateDto,
|
||||
} from './note-permissions.dto';
|
||||
import { NoteDto } from './note.dto';
|
||||
import { Note } from './note.entity';
|
||||
import { NoteUtils } from './note.utils';
|
||||
import { Tag } from './tag.entity';
|
||||
|
||||
@Injectable()
|
||||
export class NotesService {
|
||||
constructor(
|
||||
private readonly logger: ConsoleLoggerService,
|
||||
@InjectRepository(Note) private noteRepository: Repository<Note>,
|
||||
@InjectRepository(Tag) private tagRepository: Repository<Tag>,
|
||||
@Inject(UsersService) private usersService: UsersService,
|
||||
@Inject(forwardRef(() => RevisionsService))
|
||||
private revisionsService: RevisionsService,
|
||||
|
@ -102,10 +103,10 @@ export class NotesService {
|
|||
// TODO: Convert DB UUID to base64
|
||||
id: note.id,
|
||||
alias: note.alias,
|
||||
title: NoteUtils.parseTitle(note),
|
||||
title: note.title,
|
||||
// TODO: Get actual createTime
|
||||
createTime: new Date(),
|
||||
description: NoteUtils.parseDescription(note),
|
||||
description: note.description,
|
||||
editedBy: note.authorColors.map(authorColor => authorColor.user.userName),
|
||||
// TODO: Extract into method
|
||||
permissions: {
|
||||
|
@ -119,7 +120,7 @@ export class NotesService {
|
|||
canEdit: noteGroupPermission.canEdit,
|
||||
})),
|
||||
},
|
||||
tags: NoteUtils.parseTags(note),
|
||||
tags: note.tags.map(tag => tag.name),
|
||||
updateTime: (await this.getLastRevision(note)).createdAt,
|
||||
// TODO: Get actual updateUser
|
||||
updateUser: {
|
||||
|
|
19
src/notes/tag.entity.ts
Normal file
19
src/notes/tag.entity.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Note } from './note.entity';
|
||||
|
||||
@Entity()
|
||||
export class Tag {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
})
|
||||
name: string;
|
||||
|
||||
@ManyToMany(
|
||||
_ => Note,
|
||||
note => note.tags,
|
||||
)
|
||||
notes: Note[];
|
||||
}
|
|
@ -10,6 +10,7 @@ import { User } from '../users/user.entity';
|
|||
import { Authorship } from './authorship.entity';
|
||||
import { Revision } from './revision.entity';
|
||||
import { RevisionsService } from './revisions.service';
|
||||
import { Tag } from '../notes/tag.entity';
|
||||
|
||||
describe('RevisionsService', () => {
|
||||
let service: RevisionsService;
|
||||
|
@ -39,6 +40,8 @@ describe('RevisionsService', () => {
|
|||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Revision))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Tag))
|
||||
.useValue({})
|
||||
.compile();
|
||||
|
||||
service = module.get<RevisionsService>(RevisionsService);
|
||||
|
|
|
@ -76,6 +76,7 @@ export class RevisionsService {
|
|||
createRevision(content: string) {
|
||||
// TODO: Add previous revision
|
||||
// TODO: Calculate patch
|
||||
// TODO: Save metadata
|
||||
return this.revisionRepository.create({
|
||||
content: content,
|
||||
length: content.length,
|
||||
|
|
|
@ -98,20 +98,28 @@ describe('Notes', () => {
|
|||
).toEqual('New note text');
|
||||
});
|
||||
|
||||
it.skip(`PUT /notes/{note}/metadata`, () => {
|
||||
// TODO
|
||||
return request(app.getHttpServer())
|
||||
.post('/notes/test5/metadata')
|
||||
.set('Content-Type', 'text/markdown')
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it.skip(`GET /notes/{note}/metadata`, () => {
|
||||
notesService.createNote('This is a test note.', 'test6');
|
||||
return request(app.getHttpServer())
|
||||
it(`GET /notes/{note}/metadata`, async () => {
|
||||
await notesService.createNote('This is a test note.', 'test6');
|
||||
const metadata = await request(app.getHttpServer())
|
||||
.get('/notes/test6/metadata')
|
||||
.expect(200);
|
||||
// TODO: Find out how to check the structure of the returned JSON
|
||||
expect(typeof metadata.body.id).toEqual('string');
|
||||
expect(metadata.body.alias).toEqual('test6');
|
||||
expect(metadata.body.title).toBeNull();
|
||||
expect(metadata.body.description).toBeNull();
|
||||
expect(typeof metadata.body.createTime).toEqual('string');
|
||||
expect(metadata.body.editedBy).toEqual([]);
|
||||
expect(metadata.body.permissions.owner).toBeNull();
|
||||
expect(metadata.body.permissions.sharedToUsers).toEqual([]);
|
||||
expect(metadata.body.permissions.sharedToUsers).toEqual([]);
|
||||
expect(metadata.body.tags).toEqual([]);
|
||||
expect(typeof metadata.body.updateTime).toEqual('string');
|
||||
expect(typeof metadata.body.updateUser.displayName).toEqual('string');
|
||||
expect(typeof metadata.body.updateUser.userName).toEqual('string');
|
||||
expect(typeof metadata.body.updateUser.email).toEqual('string');
|
||||
expect(typeof metadata.body.updateUser.photo).toEqual('string');
|
||||
expect(typeof metadata.body.viewCount).toEqual('number');
|
||||
expect(metadata.body.editedBy).toEqual([]);
|
||||
});
|
||||
|
||||
it(`GET /notes/{note}/revisions`, async () => {
|
||||
|
|
Loading…
Reference in a new issue