mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-18 17:25:16 -04:00
Merge pull request #500 from codimd/routes/notes/services
This commit is contained in:
commit
d07d8fe278
21 changed files with 513 additions and 229 deletions
|
@ -58,7 +58,7 @@ entity "Session" as seesion {
|
||||||
|
|
||||||
|
|
||||||
entity "Revision" {
|
entity "Revision" {
|
||||||
*id : uuid <<generated>>
|
*id : number <<generated>>
|
||||||
--
|
--
|
||||||
*noteId : uuid <<FK Note>>
|
*noteId : uuid <<FK Note>>
|
||||||
*content : text
|
*content : text
|
||||||
|
@ -78,7 +78,7 @@ entity "Authorship" {
|
||||||
}
|
}
|
||||||
|
|
||||||
entity "RevisionAuthorship" {
|
entity "RevisionAuthorship" {
|
||||||
*revisionId : uuid <<FK Revision>>
|
*revisionId : number <<FK Revision>>
|
||||||
*authorshipId : uuid <<FK Authorship>>
|
*authorshipId : uuid <<FK Authorship>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,11 +115,11 @@ entity "Group" {
|
||||||
*canEdit : boolean
|
*canEdit : boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
Note "1" -- "1..*" Revision
|
Note "1" - "1..*" Revision
|
||||||
Revision "0..*" -- "0..*" Authorship
|
Revision "0..*" - "0..*" Authorship
|
||||||
(Revision, Authorship) .. RevisionAuthorship
|
(Revision, Authorship) .. RevisionAuthorship
|
||||||
Authorship "0..*" -- "1" User
|
Authorship "0..*" -- "1" User
|
||||||
Note "1" -- "0..*" User : owner
|
Note "0..*" -- "1" User : owner
|
||||||
Note "1" -- "0..*" NoteUserPermission
|
Note "1" -- "0..*" NoteUserPermission
|
||||||
NoteUserPermission "1" -- "1" User
|
NoteUserPermission "1" -- "1" User
|
||||||
Note "1" -- "0..*" NoteGroupPermission
|
Note "1" -- "0..*" NoteGroupPermission
|
||||||
|
|
|
@ -368,7 +368,7 @@ paths:
|
||||||
- note
|
- note
|
||||||
summary: Returns a list of the available note revisions
|
summary: Returns a list of the available note revisions
|
||||||
operationId: getAllRevisionsOfNote
|
operationId: getAllRevisionsOfNote
|
||||||
description: The list is returned as a JSON object with an array of revision-id and length associations. The revision-id equals to the timestamp when the revision was saved.
|
description: The list contains the revision-id, the length and a ISO-timestamp of the creation date.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Revisions of the note.
|
description: Revisions of the note.
|
||||||
|
@ -399,7 +399,7 @@ paths:
|
||||||
description: The revision is returned as a JSON object with the content of the note and the authorship.
|
description: The revision is returned as a JSON object with the content of the note and the authorship.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Revision of the note for the given timestamp.
|
description: Revision of the note for the given id.
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
|
@ -421,7 +421,7 @@ paths:
|
||||||
- name: revision-id
|
- name: revision-id
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
description: The id (timestamp) of the revision to fetch.
|
description: The id of the revision to fetch.
|
||||||
content:
|
content:
|
||||||
text/plain:
|
text/plain:
|
||||||
example: 1570921051959
|
example: 1570921051959
|
||||||
|
@ -579,7 +579,7 @@ components:
|
||||||
description: A tag
|
description: A tag
|
||||||
updateTime:
|
updateTime:
|
||||||
type: integer
|
type: integer
|
||||||
description: UNIX-timestamp of when the note was last changed.
|
description: ISO-timestamp of when the note was last changed.
|
||||||
updateUser:
|
updateUser:
|
||||||
$ref: "#/components/schemas/UserInfo"
|
$ref: "#/components/schemas/UserInfo"
|
||||||
viewCount:
|
viewCount:
|
||||||
|
@ -588,7 +588,7 @@ components:
|
||||||
description: How often the published version of the note was viewed.
|
description: How often the published version of the note was viewed.
|
||||||
createTime:
|
createTime:
|
||||||
type: string
|
type: string
|
||||||
description: The timestamp when the note was created in ISO 8601 format.
|
description: The ISO-timestamp when the note was created in ISO 8601 format.
|
||||||
editedBy:
|
editedBy:
|
||||||
type: array
|
type: array
|
||||||
description: List of usernames who edited the note.
|
description: List of usernames who edited the note.
|
||||||
|
@ -614,20 +614,19 @@ components:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
NoteRevisionsMetadata:
|
NoteRevisionsMetadata:
|
||||||
type: object
|
type: array
|
||||||
properties:
|
items:
|
||||||
revision:
|
type: object
|
||||||
type: array
|
properties:
|
||||||
description: Array that holds all revision-info objects.
|
id:
|
||||||
items:
|
type: integer
|
||||||
type: object
|
description: The id of the revision
|
||||||
properties:
|
createdAt:
|
||||||
time:
|
type: integer
|
||||||
type: integer
|
description: ISO-timestamp of when the revision was saved. Is also the revision-id.
|
||||||
description: UNIX-timestamp of when the revision was saved. Is also the revision-id.
|
length:
|
||||||
length:
|
type: integer
|
||||||
type: integer
|
description: Length of the document to the timepoint the revision was saved.
|
||||||
description: Length of the document to the timepoint the revision was saved.
|
|
||||||
NoteRevision:
|
NoteRevision:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
"class-transformer": "^0.2.3",
|
"class-transformer": "^0.2.3",
|
||||||
"class-validator": "^0.12.2",
|
"class-validator": "^0.12.2",
|
||||||
"connect-typeorm": "^1.1.4",
|
"connect-typeorm": "^1.1.4",
|
||||||
|
"raw-body": "^2.4.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^6.5.4",
|
"rxjs": "^6.5.4",
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { HistoryModule } from '../../../history/history.module';
|
||||||
import { AuthorColor } from '../../../notes/author-color.entity';
|
import { AuthorColor } from '../../../notes/author-color.entity';
|
||||||
import { Note } from '../../../notes/note.entity';
|
import { Note } from '../../../notes/note.entity';
|
||||||
import { NotesModule } from '../../../notes/notes.module';
|
import { NotesModule } from '../../../notes/notes.module';
|
||||||
|
import { Authorship } from '../../../revisions/authorship.entity';
|
||||||
|
import { Revision } from '../../../revisions/revision.entity';
|
||||||
import { AuthToken } from '../../../users/auth-token.entity';
|
import { AuthToken } from '../../../users/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';
|
||||||
|
@ -28,6 +30,10 @@ describe('Me Controller', () => {
|
||||||
.useValue({})
|
.useValue({})
|
||||||
.overrideProvider(getRepositoryToken(AuthorColor))
|
.overrideProvider(getRepositoryToken(AuthorColor))
|
||||||
.useValue({})
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(Authorship))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(Revision))
|
||||||
|
.useValue({})
|
||||||
.compile();
|
.compile();
|
||||||
|
|
||||||
controller = module.get<MeController>(MeController);
|
controller = module.get<MeController>(MeController);
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { AuthorColor } from '../../../notes/author-color.entity';
|
||||||
import { Note } from '../../../notes/note.entity';
|
import { Note } from '../../../notes/note.entity';
|
||||||
import { NotesService } from '../../../notes/notes.service';
|
import { NotesService } from '../../../notes/notes.service';
|
||||||
import { Authorship } from '../../../revisions/authorship.entity';
|
import { Authorship } from '../../../revisions/authorship.entity';
|
||||||
import { Revision } from '../../../revisions/revision.entity';
|
import { Revision } from '../../../revisions/revision.entity';
|
||||||
import { RevisionsModule } from '../../../revisions/revisions.module';
|
import { RevisionsModule } from '../../../revisions/revisions.module';
|
||||||
|
import { AuthToken } from '../../../users/auth-token.entity';
|
||||||
|
import { Identity } from '../../../users/identity.entity';
|
||||||
|
import { User } from '../../../users/user.entity';
|
||||||
|
import { UsersModule } from '../../../users/users.module';
|
||||||
import { NotesController } from './notes.controller';
|
import { NotesController } from './notes.controller';
|
||||||
|
|
||||||
describe('Notes Controller', () => {
|
describe('Notes Controller', () => {
|
||||||
|
@ -13,8 +18,14 @@ describe('Notes Controller', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [NotesController],
|
controllers: [NotesController],
|
||||||
providers: [NotesService],
|
providers: [
|
||||||
imports: [RevisionsModule],
|
NotesService,
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(Note),
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
imports: [RevisionsModule, UsersModule],
|
||||||
})
|
})
|
||||||
.overrideProvider(getRepositoryToken(Note))
|
.overrideProvider(getRepositoryToken(Note))
|
||||||
.useValue({})
|
.useValue({})
|
||||||
|
@ -22,6 +33,16 @@ describe('Notes Controller', () => {
|
||||||
.useValue({})
|
.useValue({})
|
||||||
.overrideProvider(getRepositoryToken(Authorship))
|
.overrideProvider(getRepositoryToken(Authorship))
|
||||||
.useValue({})
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(AuthorColor))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(User))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(AuthToken))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(Identity))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(Note))
|
||||||
|
.useValue({})
|
||||||
.compile();
|
.compile();
|
||||||
|
|
||||||
controller = module.get<NotesController>(NotesController);
|
controller = module.get<NotesController>(NotesController);
|
||||||
|
|
|
@ -1,53 +1,98 @@
|
||||||
import {
|
import {
|
||||||
|
BadRequestException,
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
Delete,
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
Header,
|
Header,
|
||||||
|
Logger,
|
||||||
Param,
|
Param,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
Put,
|
||||||
|
Req,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { Request } from 'express';
|
||||||
|
import * as getRawBody from 'raw-body';
|
||||||
import { NotePermissionsUpdateDto } from '../../../notes/note-permissions.dto';
|
import { NotePermissionsUpdateDto } from '../../../notes/note-permissions.dto';
|
||||||
import { NotesService } from '../../../notes/notes.service';
|
import { NotesService } from '../../../notes/notes.service';
|
||||||
import { RevisionsService } from '../../../revisions/revisions.service';
|
import { RevisionsService } from '../../../revisions/revisions.service';
|
||||||
|
|
||||||
@Controller('notes')
|
@Controller('notes')
|
||||||
export class NotesController {
|
export class NotesController {
|
||||||
|
private readonly logger = new Logger(NotesController.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private noteService: NotesService,
|
private noteService: NotesService,
|
||||||
private revisionsService: RevisionsService,
|
private revisionsService: RevisionsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the raw markdown from the request body and create a new note with it
|
||||||
|
*
|
||||||
|
* Implementation inspired by https://stackoverflow.com/questions/52283713/how-do-i-pass-plain-text-as-my-request-body-using-nestjs
|
||||||
|
*/
|
||||||
@Post()
|
@Post()
|
||||||
createNote(@Body() noteContent: string) {
|
async createNote(@Req() req: Request) {
|
||||||
return this.noteService.createNote(noteContent);
|
// we have to check req.readable because of raw-body issue #57
|
||||||
|
// https://github.com/stream-utils/raw-body/issues/57
|
||||||
|
if (req.readable) {
|
||||||
|
let bodyText: string = await getRawBody(req, 'utf-8');
|
||||||
|
bodyText = bodyText.trim();
|
||||||
|
this.logger.debug('Got raw markdown:\n' + bodyText);
|
||||||
|
return this.noteService.createNoteDto(bodyText);
|
||||||
|
} else {
|
||||||
|
// TODO: Better error message
|
||||||
|
throw new BadRequestException('Invalid body');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':noteIdOrAlias')
|
@Get(':noteIdOrAlias')
|
||||||
getNote(@Param('noteIdOrAlias') noteIdOrAlias: string) {
|
getNote(@Param('noteIdOrAlias') noteIdOrAlias: string) {
|
||||||
return this.noteService.getNoteByIdOrAlias(noteIdOrAlias);
|
return this.noteService.getNoteDtoByIdOrAlias(noteIdOrAlias);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':noteAlias')
|
@Post(':noteAlias')
|
||||||
createNamedNote(
|
async createNamedNote(
|
||||||
@Param('noteAlias') noteAlias: string,
|
@Param('noteAlias') noteAlias: string,
|
||||||
@Body() noteContent: string,
|
@Req() req: Request,
|
||||||
) {
|
) {
|
||||||
return this.noteService.createNote(noteContent, noteAlias);
|
// we have to check req.readable because of raw-body issue #57
|
||||||
|
// https://github.com/stream-utils/raw-body/issues/57
|
||||||
|
if (req.readable) {
|
||||||
|
let bodyText: string = await getRawBody(req, 'utf-8');
|
||||||
|
bodyText = bodyText.trim();
|
||||||
|
this.logger.debug('Got raw markdown:\n' + bodyText);
|
||||||
|
return this.noteService.createNoteDto(bodyText, noteAlias);
|
||||||
|
} else {
|
||||||
|
// TODO: Better error message
|
||||||
|
throw new BadRequestException('Invalid body');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':noteIdOrAlias')
|
@Delete(':noteIdOrAlias')
|
||||||
deleteNote(@Param('noteIdOrAlias') noteIdOrAlias: string) {
|
async deleteNote(@Param('noteIdOrAlias') noteIdOrAlias: string) {
|
||||||
return this.noteService.deleteNoteByIdOrAlias(noteIdOrAlias);
|
this.logger.debug('Deleting note: ' + noteIdOrAlias);
|
||||||
|
await this.noteService.deleteNoteByIdOrAlias(noteIdOrAlias);
|
||||||
|
this.logger.debug('Successfully deleted ' + noteIdOrAlias);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':noteIdOrAlias')
|
@Put(':noteIdOrAlias')
|
||||||
updateNote(
|
async updateNote(
|
||||||
@Param('noteIdOrAlias') noteIdOrAlias: string,
|
@Param('noteIdOrAlias') noteIdOrAlias: string,
|
||||||
@Body() noteContent: string,
|
@Req() req: Request,
|
||||||
) {
|
) {
|
||||||
return this.noteService.updateNoteByIdOrAlias(noteIdOrAlias, noteContent);
|
// we have to check req.readable because of raw-body issue #57
|
||||||
|
// https://github.com/stream-utils/raw-body/issues/57
|
||||||
|
if (req.readable) {
|
||||||
|
let bodyText: string = await getRawBody(req, 'utf-8');
|
||||||
|
bodyText = bodyText.trim();
|
||||||
|
this.logger.debug('Got raw markdown:\n' + bodyText);
|
||||||
|
return this.noteService.updateNoteByIdOrAlias(noteIdOrAlias, bodyText);
|
||||||
|
} else {
|
||||||
|
// TODO: Better error message
|
||||||
|
throw new BadRequestException('Invalid body');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':noteIdOrAlias/content')
|
@Get(':noteIdOrAlias/content')
|
||||||
|
@ -77,7 +122,7 @@ export class NotesController {
|
||||||
@Get(':noteIdOrAlias/revisions/:revisionId')
|
@Get(':noteIdOrAlias/revisions/:revisionId')
|
||||||
getNoteRevision(
|
getNoteRevision(
|
||||||
@Param('noteIdOrAlias') noteIdOrAlias: string,
|
@Param('noteIdOrAlias') noteIdOrAlias: string,
|
||||||
@Param('revisionId') revisionId: string,
|
@Param('revisionId') revisionId: number,
|
||||||
) {
|
) {
|
||||||
return this.revisionsService.getNoteRevision(noteIdOrAlias, revisionId);
|
return this.revisionsService.getNoteRevision(noteIdOrAlias, revisionId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ export class NoteDto {
|
||||||
content: string;
|
content: string;
|
||||||
|
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
metdata: NoteMetadataDto;
|
metadata: NoteMetadataDto;
|
||||||
|
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
|
|
|
@ -16,64 +16,64 @@ import { AuthorColor } from './author-color.entity';
|
||||||
export class Note {
|
export class Note {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
unique: true,
|
unique: true,
|
||||||
})
|
})
|
||||||
shortid: string;
|
shortid: string;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
unique: true,
|
unique: true,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
alias: string;
|
alias: string;
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
_ => NoteGroupPermission,
|
_ => NoteGroupPermission,
|
||||||
groupPermission => groupPermission.note,
|
groupPermission => groupPermission.note,
|
||||||
)
|
)
|
||||||
groupPermissions: NoteGroupPermission[];
|
groupPermissions: NoteGroupPermission[];
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
_ => NoteUserPermission,
|
_ => NoteUserPermission,
|
||||||
userPermission => userPermission.note,
|
userPermission => userPermission.note,
|
||||||
)
|
)
|
||||||
userPermissions: NoteUserPermission[];
|
userPermissions: NoteUserPermission[];
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
default: 0,
|
default: 0,
|
||||||
})
|
})
|
||||||
viewcount: number;
|
viewcount: number;
|
||||||
|
|
||||||
@ManyToOne(
|
@ManyToOne(
|
||||||
_ => User,
|
_ => User,
|
||||||
user => user.ownedNotes,
|
user => user.ownedNotes,
|
||||||
{ onDelete: 'CASCADE' },
|
{ onDelete: 'CASCADE' },
|
||||||
)
|
)
|
||||||
owner: User;
|
owner: User;
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
_ => Revision,
|
_ => Revision,
|
||||||
revision => revision.note,
|
revision => revision.note,
|
||||||
|
{ cascade: true },
|
||||||
)
|
)
|
||||||
revisions: Revision[];
|
revisions: Promise<Revision[]>;
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
_ => AuthorColor,
|
_ => AuthorColor,
|
||||||
authorColor => authorColor.note,
|
authorColor => authorColor.note,
|
||||||
)
|
)
|
||||||
authorColors: AuthorColor[];
|
authorColors: AuthorColor[];
|
||||||
|
|
||||||
constructor(shortid: string, alias: string, owner: User) {
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
if (shortid) {
|
private constructor() {}
|
||||||
this.shortid = shortid;
|
|
||||||
} else {
|
public static create(owner?: User, alias?: string, shortid?: string) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
if (!shortid) {
|
||||||
this.shortid = shortIdGenerate() as string;
|
shortid = shortIdGenerate();
|
||||||
}
|
}
|
||||||
this.alias = alias;
|
const newNote = new Note();
|
||||||
this.owner = owner;
|
newNote.shortid = shortid;
|
||||||
|
newNote.alias = alias;
|
||||||
|
newNote.viewcount = 0;
|
||||||
|
newNote.owner = owner;
|
||||||
|
newNote.authorColors = [];
|
||||||
|
newNote.userPermissions = [];
|
||||||
|
newNote.groupPermissions = [];
|
||||||
|
return newNote;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
src/notes/note.utils.ts
Normal file
18
src/notes/note.utils.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
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'];
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,17 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { RevisionsModule } from '../revisions/revisions.module';
|
||||||
|
import { UsersModule } from '../users/users.module';
|
||||||
import { AuthorColor } from './author-color.entity';
|
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';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Note, AuthorColor])],
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([Note, AuthorColor]),
|
||||||
|
forwardRef(() => RevisionsModule),
|
||||||
|
UsersModule,
|
||||||
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [NotesService],
|
providers: [NotesService],
|
||||||
exports: [NotesService],
|
exports: [NotesService],
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { Authorship } from '../revisions/authorship.entity';
|
||||||
|
import { Revision } from '../revisions/revision.entity';
|
||||||
|
import { RevisionsModule } from '../revisions/revisions.module';
|
||||||
|
import { AuthToken } from '../users/auth-token.entity';
|
||||||
|
import { Identity } from '../users/identity.entity';
|
||||||
|
import { User } from '../users/user.entity';
|
||||||
|
import { UsersModule } from '../users/users.module';
|
||||||
|
import { AuthorColor } from './author-color.entity';
|
||||||
|
import { Note } from './note.entity';
|
||||||
import { NotesService } from './notes.service';
|
import { NotesService } from './notes.service';
|
||||||
|
|
||||||
describe('NotesService', () => {
|
describe('NotesService', () => {
|
||||||
|
@ -6,9 +16,30 @@ describe('NotesService', () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [NotesService],
|
providers: [
|
||||||
}).compile();
|
NotesService,
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(Note),
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
imports: [UsersModule, RevisionsModule],
|
||||||
|
})
|
||||||
|
.overrideProvider(getRepositoryToken(User))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(AuthToken))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(Identity))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(Authorship))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(AuthorColor))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(Revision))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(Note))
|
||||||
|
.useValue({})
|
||||||
|
.compile();
|
||||||
service = module.get<NotesService>(NotesService);
|
service = module.get<NotesService>(NotesService);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,30 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
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 } from './note-metadata.dto';
|
||||||
import {
|
import {
|
||||||
NotePermissionsDto,
|
NotePermissionsDto,
|
||||||
NotePermissionsUpdateDto,
|
NotePermissionsUpdateDto,
|
||||||
} from './note-permissions.dto';
|
} from './note-permissions.dto';
|
||||||
import { NoteDto } from './note.dto';
|
import { NoteDto } from './note.dto';
|
||||||
|
import { Note } from './note.entity';
|
||||||
|
import { NoteUtils } from './note.utils';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NotesService {
|
export class NotesService {
|
||||||
private readonly logger = new Logger(NotesService.name);
|
private readonly logger = new Logger(NotesService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Note) private noteRepository: Repository<Note>,
|
||||||
|
@Inject(UsersService) private usersService: UsersService,
|
||||||
|
@Inject(forwardRef(() => RevisionsService))
|
||||||
|
private revisionsService: RevisionsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
getUserNotes(username: string): NoteMetadataDto[] {
|
getUserNotes(username: string): NoteMetadataDto[] {
|
||||||
this.logger.warn('Using hardcoded data!');
|
this.logger.warn('Using hardcoded data!');
|
||||||
return [
|
return [
|
||||||
|
@ -43,140 +58,70 @@ export class NotesService {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
createNote(noteContent: string, alias?: NoteMetadataDto['alias']): NoteDto {
|
async createNoteDto(
|
||||||
this.logger.warn('Using hardcoded data!');
|
noteContent: string,
|
||||||
return {
|
alias?: NoteMetadataDto['alias'],
|
||||||
content: noteContent,
|
owner?: User,
|
||||||
metdata: {
|
): Promise<NoteDto> {
|
||||||
alias: alias,
|
const note = await this.createNote(noteContent, alias, owner);
|
||||||
createTime: new Date(),
|
return this.toNoteDto(note);
|
||||||
description: 'Very descriptive text.',
|
|
||||||
editedBy: [],
|
|
||||||
id: 'foobar-barfoo',
|
|
||||||
permission: {
|
|
||||||
owner: {
|
|
||||||
displayName: 'foo',
|
|
||||||
userName: 'fooUser',
|
|
||||||
email: 'foo@example.com',
|
|
||||||
photo: '',
|
|
||||||
},
|
|
||||||
sharedToUsers: [],
|
|
||||||
sharedToGroups: [],
|
|
||||||
},
|
|
||||||
tags: [],
|
|
||||||
title: 'Title!',
|
|
||||||
updateTime: new Date(),
|
|
||||||
updateUser: {
|
|
||||||
displayName: 'foo',
|
|
||||||
userName: 'fooUser',
|
|
||||||
email: 'foo@example.com',
|
|
||||||
photo: '',
|
|
||||||
},
|
|
||||||
viewCount: 42,
|
|
||||||
},
|
|
||||||
editedByAtPosition: [],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getNoteByIdOrAlias(noteIdOrAlias: string) {
|
async createNote(
|
||||||
this.logger.warn('Using hardcoded data!');
|
noteContent: string,
|
||||||
return {
|
alias?: NoteMetadataDto['alias'],
|
||||||
content: 'noteContent',
|
owner?: User,
|
||||||
metdata: {
|
): Promise<Note> {
|
||||||
alias: null,
|
const newNote = Note.create();
|
||||||
createTime: new Date(),
|
newNote.revisions = Promise.resolve([
|
||||||
description: 'Very descriptive text.',
|
//TODO: Calculate patch
|
||||||
editedBy: [],
|
Revision.create(noteContent, noteContent),
|
||||||
id: noteIdOrAlias,
|
]);
|
||||||
permission: {
|
if (alias) {
|
||||||
owner: {
|
newNote.alias = alias;
|
||||||
displayName: 'foo',
|
}
|
||||||
userName: 'fooUser',
|
if (owner) {
|
||||||
email: 'foo@example.com',
|
newNote.owner = owner;
|
||||||
photo: '',
|
}
|
||||||
},
|
return this.noteRepository.save(newNote);
|
||||||
sharedToUsers: [],
|
|
||||||
sharedToGroups: [],
|
|
||||||
},
|
|
||||||
tags: [],
|
|
||||||
title: 'Title!',
|
|
||||||
updateTime: new Date(),
|
|
||||||
updateUser: {
|
|
||||||
displayName: 'foo',
|
|
||||||
userName: 'fooUser',
|
|
||||||
email: 'foo@example.com',
|
|
||||||
photo: '',
|
|
||||||
},
|
|
||||||
viewCount: 42,
|
|
||||||
},
|
|
||||||
editedByAtPosition: [],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteNoteByIdOrAlias(noteIdOrAlias: string) {
|
async getCurrentContent(note: Note) {
|
||||||
this.logger.warn('Using hardcoded data!');
|
return (await this.getLastRevision(note)).content;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNoteByIdOrAlias(noteIdOrAlias: string, noteContent: string) {
|
async getLastRevision(note: Note): Promise<Revision> {
|
||||||
this.logger.warn('Using hardcoded data!');
|
return this.revisionsService.getLatestRevision(note.id);
|
||||||
return {
|
|
||||||
content: noteContent,
|
|
||||||
metdata: {
|
|
||||||
alias: null,
|
|
||||||
createTime: new Date(),
|
|
||||||
description: 'Very descriptive text.',
|
|
||||||
editedBy: [],
|
|
||||||
id: noteIdOrAlias,
|
|
||||||
permission: {
|
|
||||||
owner: {
|
|
||||||
displayName: 'foo',
|
|
||||||
userName: 'fooUser',
|
|
||||||
email: 'foo@example.com',
|
|
||||||
photo: '',
|
|
||||||
},
|
|
||||||
sharedToUsers: [],
|
|
||||||
sharedToGroups: [],
|
|
||||||
},
|
|
||||||
tags: [],
|
|
||||||
title: 'Title!',
|
|
||||||
updateTime: new Date(),
|
|
||||||
updateUser: {
|
|
||||||
displayName: 'foo',
|
|
||||||
userName: 'fooUser',
|
|
||||||
email: 'foo@example.com',
|
|
||||||
photo: '',
|
|
||||||
},
|
|
||||||
viewCount: 42,
|
|
||||||
},
|
|
||||||
editedByAtPosition: [],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getNoteMetadata(noteIdOrAlias: string): NoteMetadataDto {
|
async getMetadata(note: Note): Promise<NoteMetadataDto> {
|
||||||
this.logger.warn('Using hardcoded data!');
|
|
||||||
return {
|
return {
|
||||||
alias: null,
|
// TODO: Convert DB UUID to base64
|
||||||
|
id: note.id,
|
||||||
|
alias: note.alias,
|
||||||
|
title: NoteUtils.parseTitle(note),
|
||||||
|
// TODO: Get actual createTime
|
||||||
createTime: new Date(),
|
createTime: new Date(),
|
||||||
description: 'Very descriptive text.',
|
description: NoteUtils.parseDescription(note),
|
||||||
editedBy: [],
|
editedBy: note.authorColors.map(authorColor => authorColor.user.userName),
|
||||||
id: noteIdOrAlias,
|
// TODO: Extract into method
|
||||||
permission: {
|
permission: {
|
||||||
owner: {
|
owner: this.usersService.toUserDto(note.owner),
|
||||||
displayName: 'foo',
|
sharedToUsers: note.userPermissions.map(noteUserPermission => ({
|
||||||
userName: 'fooUser',
|
user: this.usersService.toUserDto(noteUserPermission.user),
|
||||||
email: 'foo@example.com',
|
canEdit: noteUserPermission.canEdit,
|
||||||
photo: '',
|
})),
|
||||||
},
|
sharedToGroups: note.groupPermissions.map(noteGroupPermission => ({
|
||||||
sharedToUsers: [],
|
group: noteGroupPermission.group,
|
||||||
sharedToGroups: [],
|
canEdit: noteGroupPermission.canEdit,
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
tags: [],
|
tags: NoteUtils.parseTags(note),
|
||||||
title: 'Title!',
|
updateTime: (await this.getLastRevision(note)).createdAt,
|
||||||
updateTime: new Date(),
|
// TODO: Get actual updateUser
|
||||||
updateUser: {
|
updateUser: {
|
||||||
displayName: 'foo',
|
displayName: 'Hardcoded User',
|
||||||
userName: 'fooUser',
|
userName: 'hardcoded',
|
||||||
email: 'foo@example.com',
|
email: 'foo@example.com',
|
||||||
photo: '',
|
photo: '',
|
||||||
},
|
},
|
||||||
|
@ -184,6 +129,47 @@ export class NotesService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getNoteByIdOrAlias(noteIdOrAlias: string): Promise<Note> {
|
||||||
|
const note = await this.noteRepository.findOne({
|
||||||
|
where: [{ id: noteIdOrAlias }, { alias: noteIdOrAlias }],
|
||||||
|
relations: [
|
||||||
|
'authorColors',
|
||||||
|
'owner',
|
||||||
|
'groupPermissions',
|
||||||
|
'userPermissions',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (note === undefined) {
|
||||||
|
//TODO: Improve error handling
|
||||||
|
throw new Error('Note not found');
|
||||||
|
}
|
||||||
|
return note;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getNoteDtoByIdOrAlias(noteIdOrAlias: string): Promise<NoteDto> {
|
||||||
|
const note = await this.getNoteByIdOrAlias(noteIdOrAlias);
|
||||||
|
return this.toNoteDto(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteNoteByIdOrAlias(noteIdOrAlias: string) {
|
||||||
|
const note = await this.getNoteByIdOrAlias(noteIdOrAlias);
|
||||||
|
return await this.noteRepository.remove(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateNoteByIdOrAlias(noteIdOrAlias: string, noteContent: string) {
|
||||||
|
const note = await this.getNoteByIdOrAlias(noteIdOrAlias);
|
||||||
|
const revisions = await note.revisions;
|
||||||
|
//TODO: Calculate patch
|
||||||
|
revisions.push(Revision.create(noteContent, noteContent));
|
||||||
|
note.revisions = Promise.resolve(revisions);
|
||||||
|
await this.noteRepository.save(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getNoteMetadata(noteIdOrAlias: string): Promise<NoteMetadataDto> {
|
||||||
|
const note = await this.getNoteByIdOrAlias(noteIdOrAlias);
|
||||||
|
return this.getMetadata(note);
|
||||||
|
}
|
||||||
|
|
||||||
updateNotePermissions(
|
updateNotePermissions(
|
||||||
noteIdOrAlias: string,
|
noteIdOrAlias: string,
|
||||||
newPermissions: NotePermissionsUpdateDto,
|
newPermissions: NotePermissionsUpdateDto,
|
||||||
|
@ -201,8 +187,16 @@ export class NotesService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getNoteContent(noteIdOrAlias: string) {
|
async getNoteContent(noteIdOrAlias: string): Promise<string> {
|
||||||
this.logger.warn('Using hardcoded data!');
|
const note = await this.getNoteByIdOrAlias(noteIdOrAlias);
|
||||||
return '# Markdown';
|
return this.getCurrentContent(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
async toNoteDto(note: Note): Promise<NoteDto> {
|
||||||
|
return {
|
||||||
|
content: await this.getCurrentContent(note),
|
||||||
|
metadata: await this.getMetadata(note),
|
||||||
|
editedByAtPosition: [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@ import { IsDate, IsNumber, IsString } from 'class-validator';
|
||||||
import { Revision } from './revision.entity';
|
import { Revision } from './revision.entity';
|
||||||
|
|
||||||
export class RevisionMetadataDto {
|
export class RevisionMetadataDto {
|
||||||
@IsString()
|
@IsNumber()
|
||||||
id: Revision['id'];
|
id: Revision['id'];
|
||||||
|
|
||||||
@IsDate()
|
@IsDate()
|
||||||
updatedAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
length: number;
|
length: number;
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { IsString } from 'class-validator';
|
import { IsDate, IsNumber, IsString } from 'class-validator';
|
||||||
import { Revision } from './revision.entity';
|
import { Revision } from './revision.entity';
|
||||||
|
|
||||||
export class RevisionDto {
|
export class RevisionDto {
|
||||||
@IsString()
|
@IsNumber()
|
||||||
id: Revision['id'];
|
id: Revision['id'];
|
||||||
@IsString()
|
@IsString()
|
||||||
content: string;
|
content: string;
|
||||||
@IsString()
|
@IsString()
|
||||||
patch: string;
|
patch: string;
|
||||||
|
@IsDate()
|
||||||
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ import { Authorship } from './authorship.entity';
|
||||||
*/
|
*/
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Revision {
|
export class Revision {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn()
|
||||||
id: string;
|
id: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The patch from the previous revision to this one.
|
* The patch from the previous revision to this one.
|
||||||
|
@ -65,4 +65,15 @@ export class Revision {
|
||||||
)
|
)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
authorships: Authorship[];
|
authorships: Authorship[];
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
static create(content: string, patch: string): Revision {
|
||||||
|
const newRevision = new Revision();
|
||||||
|
newRevision.patch = patch;
|
||||||
|
newRevision.content = content;
|
||||||
|
newRevision.length = content.length;
|
||||||
|
return newRevision;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { NotesModule } from '../notes/notes.module';
|
||||||
import { Authorship } from './authorship.entity';
|
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';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Revision, Authorship])],
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([Revision, Authorship]),
|
||||||
|
forwardRef(() => NotesModule),
|
||||||
|
],
|
||||||
providers: [RevisionsService],
|
providers: [RevisionsService],
|
||||||
exports: [RevisionsService],
|
exports: [RevisionsService],
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,13 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { AuthorColor } from '../notes/author-color.entity';
|
||||||
|
import { Note } from '../notes/note.entity';
|
||||||
|
import { NotesModule } from '../notes/notes.module';
|
||||||
|
import { AuthToken } from '../users/auth-token.entity';
|
||||||
|
import { Identity } from '../users/identity.entity';
|
||||||
|
import { User } from '../users/user.entity';
|
||||||
|
import { Authorship } from './authorship.entity';
|
||||||
|
import { Revision } from './revision.entity';
|
||||||
import { RevisionsService } from './revisions.service';
|
import { RevisionsService } from './revisions.service';
|
||||||
|
|
||||||
describe('RevisionsService', () => {
|
describe('RevisionsService', () => {
|
||||||
|
@ -6,8 +15,30 @@ describe('RevisionsService', () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [RevisionsService],
|
providers: [
|
||||||
}).compile();
|
RevisionsService,
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(Revision),
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
imports: [NotesModule],
|
||||||
|
})
|
||||||
|
.overrideProvider(getRepositoryToken(Authorship))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(AuthorColor))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(User))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(AuthToken))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(Identity))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(Note))
|
||||||
|
.useValue({})
|
||||||
|
.overrideProvider(getRepositoryToken(Revision))
|
||||||
|
.useValue({})
|
||||||
|
.compile();
|
||||||
|
|
||||||
service = module.get<RevisionsService>(RevisionsService);
|
service = module.get<RevisionsService>(RevisionsService);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,27 +1,83 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { NotesService } from '../notes/notes.service';
|
||||||
import { RevisionMetadataDto } from './revision-metadata.dto';
|
import { RevisionMetadataDto } from './revision-metadata.dto';
|
||||||
import { RevisionDto } from './revision.dto';
|
import { RevisionDto } from './revision.dto';
|
||||||
|
import { Revision } from './revision.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RevisionsService {
|
export class RevisionsService {
|
||||||
private readonly logger = new Logger(RevisionsService.name);
|
private readonly logger = new Logger(RevisionsService.name);
|
||||||
getNoteRevisionMetadatas(noteIdOrAlias: string): RevisionMetadataDto[] {
|
|
||||||
this.logger.warn('Using hardcoded data!');
|
constructor(
|
||||||
return [
|
@InjectRepository(Revision)
|
||||||
{
|
private revisionRepository: Repository<Revision>,
|
||||||
id: 'some-uuid',
|
@Inject(forwardRef(() => NotesService)) private notesService: NotesService,
|
||||||
updatedAt: new Date(),
|
) {}
|
||||||
length: 42,
|
|
||||||
|
async getNoteRevisionMetadatas(
|
||||||
|
noteIdOrAlias: string,
|
||||||
|
): Promise<RevisionMetadataDto[]> {
|
||||||
|
const note = await this.notesService.getNoteByIdOrAlias(noteIdOrAlias);
|
||||||
|
const revisions = await this.revisionRepository.find({
|
||||||
|
where: {
|
||||||
|
note: note.id,
|
||||||
},
|
},
|
||||||
];
|
});
|
||||||
|
return revisions.map(revision => this.toMetadataDto(revision));
|
||||||
}
|
}
|
||||||
|
|
||||||
getNoteRevision(noteIdOrAlias: string, revisionId: string): RevisionDto {
|
async getNoteRevision(
|
||||||
this.logger.warn('Using hardcoded data!');
|
noteIdOrAlias: string,
|
||||||
|
revisionId: number,
|
||||||
|
): Promise<RevisionDto> {
|
||||||
|
const note = await this.notesService.getNoteByIdOrAlias(noteIdOrAlias);
|
||||||
|
const revision = await this.revisionRepository.findOne({
|
||||||
|
where: {
|
||||||
|
id: revisionId,
|
||||||
|
note: note,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return this.toDto(revision);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLatestRevision(noteId: string): Promise<Revision> {
|
||||||
|
return this.revisionRepository.findOne({
|
||||||
|
where: {
|
||||||
|
note: noteId,
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
createdAt: 'DESC',
|
||||||
|
id: 'DESC',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toMetadataDto(revision: Revision): RevisionMetadataDto {
|
||||||
return {
|
return {
|
||||||
id: revisionId,
|
id: revision.id,
|
||||||
content: 'Foobar',
|
length: revision.length,
|
||||||
patch: 'barfoo',
|
createdAt: revision.createdAt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toDto(revision: Revision): RevisionDto {
|
||||||
|
return {
|
||||||
|
id: revision.id,
|
||||||
|
content: revision.content,
|
||||||
|
createdAt: revision.createdAt,
|
||||||
|
patch: revision.patch,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createRevision(content: string) {
|
||||||
|
// TODO: Add previous revision
|
||||||
|
// TODO: Calculate patch
|
||||||
|
return this.revisionRepository.create({
|
||||||
|
content: content,
|
||||||
|
length: content.length,
|
||||||
|
patch: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { UserInfoDto } from './user-info.dto';
|
import { UserInfoDto } from './user-info.dto';
|
||||||
|
import { User } from './user.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
|
@ -15,4 +16,26 @@ export class UsersService {
|
||||||
photo: '',
|
photo: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPhotoUrl(user: User) {
|
||||||
|
if (user.photo) {
|
||||||
|
return user.photo;
|
||||||
|
} else {
|
||||||
|
// TODO: Create new photo, see old code
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toUserDto(user: User | null | undefined): UserInfoDto | null {
|
||||||
|
if (!user) {
|
||||||
|
this.logger.warn(`toUserDto recieved ${user} argument!`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
userName: user.userName,
|
||||||
|
displayName: user.displayName,
|
||||||
|
photo: this.getPhotoUrl(user),
|
||||||
|
email: user.email,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import { INestApplication } from '@nestjs/common';
|
import { INestApplication } from '@nestjs/common';
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import * as request from 'supertest';
|
import * as request from 'supertest';
|
||||||
import { AppModule } from '../../src/app.module';
|
import { PublicApiModule } from '../../src/api/public/public-api.module';
|
||||||
|
import { GroupsModule } from '../../src/groups/groups.module';
|
||||||
|
import { NotesModule } from '../../src/notes/notes.module';
|
||||||
import { NotesService } from '../../src/notes/notes.service';
|
import { NotesService } from '../../src/notes/notes.service';
|
||||||
|
import { PermissionsModule } from '../../src/permissions/permissions.module';
|
||||||
|
|
||||||
describe('Notes', () => {
|
describe('Notes', () => {
|
||||||
let app: INestApplication;
|
let app: INestApplication;
|
||||||
|
@ -10,29 +14,44 @@ describe('Notes', () => {
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const moduleRef = await Test.createTestingModule({
|
const moduleRef = await Test.createTestingModule({
|
||||||
imports: [AppModule],
|
imports: [
|
||||||
|
PublicApiModule,
|
||||||
|
NotesModule,
|
||||||
|
PermissionsModule,
|
||||||
|
GroupsModule,
|
||||||
|
TypeOrmModule.forRoot({
|
||||||
|
type: 'sqlite',
|
||||||
|
database: './hedgedoc-e2e.sqlite',
|
||||||
|
autoLoadEntities: true,
|
||||||
|
synchronize: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
app = moduleRef.createNestApplication();
|
app = moduleRef.createNestApplication();
|
||||||
notesService = moduleRef.get(NotesService);
|
|
||||||
await app.init();
|
await app.init();
|
||||||
|
notesService = moduleRef.get(NotesService);
|
||||||
|
const noteRepository = moduleRef.get('NoteRepository');
|
||||||
|
noteRepository.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`POST /notes`, async () => {
|
it(`POST /notes`, async () => {
|
||||||
const newNote = 'This is a test note.';
|
const newNote = 'This is a test note.';
|
||||||
const response = await request(app.getHttpServer())
|
const response = await request(app.getHttpServer())
|
||||||
.post('/notes')
|
.post('/notes')
|
||||||
|
.set('Content-Type', 'text/markdown')
|
||||||
.send(newNote)
|
.send(newNote)
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(201);
|
.expect(201);
|
||||||
expect(response.body.metadata?.id).toBeDefined();
|
expect(response.body.metadata?.id).toBeDefined();
|
||||||
expect(
|
expect(
|
||||||
notesService.getNoteByIdOrAlias(response.body.metadata.id).content,
|
(await notesService.getNoteDtoByIdOrAlias(response.body.metadata.id))
|
||||||
|
.content,
|
||||||
).toEqual(newNote);
|
).toEqual(newNote);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`GET /notes/{note}`, async () => {
|
it(`GET /notes/{note}`, async () => {
|
||||||
notesService.createNote('This is a test note.', 'test1');
|
await notesService.createNote('This is a test note.', 'test1');
|
||||||
const response = await request(app.getHttpServer())
|
const response = await request(app.getHttpServer())
|
||||||
.get('/notes/test1')
|
.get('/notes/test1')
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
|
@ -44,38 +63,44 @@ describe('Notes', () => {
|
||||||
const newNote = 'This is a test note.';
|
const newNote = 'This is a test note.';
|
||||||
const response = await request(app.getHttpServer())
|
const response = await request(app.getHttpServer())
|
||||||
.post('/notes/test2')
|
.post('/notes/test2')
|
||||||
|
.set('Content-Type', 'text/markdown')
|
||||||
.send(newNote)
|
.send(newNote)
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(201);
|
.expect(201);
|
||||||
expect(response.body.metadata?.id).toBeDefined();
|
expect(response.body.metadata?.id).toBeDefined();
|
||||||
return expect(
|
return expect(
|
||||||
notesService.getNoteByIdOrAlias(response.body.metadata.id).content,
|
(await notesService.getNoteDtoByIdOrAlias(response.body.metadata.id))
|
||||||
|
.content,
|
||||||
).toEqual(newNote);
|
).toEqual(newNote);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`DELETE /notes/{note}`, async () => {
|
it(`DELETE /notes/{note}`, async () => {
|
||||||
notesService.createNote('This is a test note.', 'test3');
|
await notesService.createNote('This is a test note.', 'test3');
|
||||||
await request(app.getHttpServer())
|
await request(app.getHttpServer())
|
||||||
.delete('/notes/test3')
|
.delete('/notes/test3')
|
||||||
.expect(200);
|
.expect(200);
|
||||||
return expect(notesService.getNoteByIdOrAlias('test3')).toBeNull();
|
return expect(notesService.getNoteByIdOrAlias('test3')).rejects.toEqual(
|
||||||
|
Error('Note not found'),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`PUT /notes/{note}`, async () => {
|
it(`PUT /notes/{note}`, async () => {
|
||||||
notesService.createNote('This is a test note.', 'test4');
|
await notesService.createNote('This is a test note.', 'test4');
|
||||||
await request(app.getHttpServer())
|
await request(app.getHttpServer())
|
||||||
.put('/notes/test4')
|
.put('/notes/test4')
|
||||||
|
.set('Content-Type', 'text/markdown')
|
||||||
.send('New note text')
|
.send('New note text')
|
||||||
.expect(200);
|
.expect(200);
|
||||||
return expect(notesService.getNoteByIdOrAlias('test4').content).toEqual(
|
return expect(
|
||||||
'New note text',
|
(await notesService.getNoteDtoByIdOrAlias('test4')).content,
|
||||||
);
|
).toEqual('New note text');
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip(`PUT /notes/{note}/metadata`, () => {
|
it.skip(`PUT /notes/{note}/metadata`, () => {
|
||||||
// TODO
|
// TODO
|
||||||
return request(app.getHttpServer())
|
return request(app.getHttpServer())
|
||||||
.post('/notes/test5/metadata')
|
.post('/notes/test5/metadata')
|
||||||
|
.set('Content-Type', 'text/markdown')
|
||||||
.expect(200);
|
.expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -88,29 +113,30 @@ describe('Notes', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`GET /notes/{note}/revisions`, async () => {
|
it(`GET /notes/{note}/revisions`, async () => {
|
||||||
notesService.createNote('This is a test note.', 'test7');
|
await notesService.createNote('This is a test note.', 'test7');
|
||||||
const response = await request(app.getHttpServer())
|
const response = await request(app.getHttpServer())
|
||||||
.get('/notes/test7/revisions')
|
.get('/notes/test7/revisions')
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
expect(response.body.revisions).toHaveLength(1);
|
expect(response.body).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`GET /notes/{note}/revisions/{revision-id}`, async () => {
|
it(`GET /notes/{note}/revisions/{revision-id}`, async () => {
|
||||||
notesService.createNote('This is a test note.', 'test8');
|
const note = await notesService.createNote('This is a test note.', 'test8');
|
||||||
|
const revision = await notesService.getLastRevision(note);
|
||||||
const response = await request(app.getHttpServer())
|
const response = await request(app.getHttpServer())
|
||||||
.get('/notes/test8/revisions/1')
|
.get('/notes/test8/revisions/' + revision.id)
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
expect(response.body.content).toEqual('This is a test note.');
|
expect(response.body.content).toEqual('This is a test note.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`GET /notes/{note}/content`, async () => {
|
it(`GET /notes/{note}/content`, async () => {
|
||||||
notesService.createNote('This is a test note.', 'test9');
|
await notesService.createNote('This is a test note.', 'test9');
|
||||||
const response = await request(app.getHttpServer())
|
const response = await request(app.getHttpServer())
|
||||||
.get('/notes/test9/content')
|
.get('/notes/test9/content')
|
||||||
.expect(200);
|
.expect(200);
|
||||||
expect(response.body).toEqual('This is a test note.');
|
expect(response.text).toEqual('This is a test note.');
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -3622,7 +3622,7 @@ http-errors@1.7.2:
|
||||||
statuses ">= 1.5.0 < 2"
|
statuses ">= 1.5.0 < 2"
|
||||||
toidentifier "1.0.0"
|
toidentifier "1.0.0"
|
||||||
|
|
||||||
http-errors@~1.7.2:
|
http-errors@1.7.3, http-errors@~1.7.2:
|
||||||
version "1.7.3"
|
version "1.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
|
||||||
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
|
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
|
||||||
|
@ -5944,6 +5944,16 @@ raw-body@2.4.0:
|
||||||
iconv-lite "0.4.24"
|
iconv-lite "0.4.24"
|
||||||
unpipe "1.0.0"
|
unpipe "1.0.0"
|
||||||
|
|
||||||
|
raw-body@^2.4.1:
|
||||||
|
version "2.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c"
|
||||||
|
integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.1.0"
|
||||||
|
http-errors "1.7.3"
|
||||||
|
iconv-lite "0.4.24"
|
||||||
|
unpipe "1.0.0"
|
||||||
|
|
||||||
rc@^1.2.7:
|
rc@^1.2.7:
|
||||||
version "1.2.8"
|
version "1.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue