mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-04-13 20:54:31 +00: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" {
|
||||
*id : uuid <<generated>>
|
||||
*id : number <<generated>>
|
||||
--
|
||||
*noteId : uuid <<FK Note>>
|
||||
*content : text
|
||||
|
@ -78,7 +78,7 @@ entity "Authorship" {
|
|||
}
|
||||
|
||||
entity "RevisionAuthorship" {
|
||||
*revisionId : uuid <<FK Revision>>
|
||||
*revisionId : number <<FK Revision>>
|
||||
*authorshipId : uuid <<FK Authorship>>
|
||||
}
|
||||
|
||||
|
@ -115,11 +115,11 @@ entity "Group" {
|
|||
*canEdit : boolean
|
||||
}
|
||||
|
||||
Note "1" -- "1..*" Revision
|
||||
Revision "0..*" -- "0..*" Authorship
|
||||
Note "1" - "1..*" Revision
|
||||
Revision "0..*" - "0..*" Authorship
|
||||
(Revision, Authorship) .. RevisionAuthorship
|
||||
Authorship "0..*" -- "1" User
|
||||
Note "1" -- "0..*" User : owner
|
||||
Note "0..*" -- "1" User : owner
|
||||
Note "1" -- "0..*" NoteUserPermission
|
||||
NoteUserPermission "1" -- "1" User
|
||||
Note "1" -- "0..*" NoteGroupPermission
|
||||
|
|
|
@ -368,7 +368,7 @@ paths:
|
|||
- note
|
||||
summary: Returns a list of the available note revisions
|
||||
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:
|
||||
'200':
|
||||
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.
|
||||
responses:
|
||||
'200':
|
||||
description: Revision of the note for the given timestamp.
|
||||
description: Revision of the note for the given id.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
|
@ -421,7 +421,7 @@ paths:
|
|||
- name: revision-id
|
||||
in: path
|
||||
required: true
|
||||
description: The id (timestamp) of the revision to fetch.
|
||||
description: The id of the revision to fetch.
|
||||
content:
|
||||
text/plain:
|
||||
example: 1570921051959
|
||||
|
@ -579,7 +579,7 @@ components:
|
|||
description: A tag
|
||||
updateTime:
|
||||
type: integer
|
||||
description: UNIX-timestamp of when the note was last changed.
|
||||
description: ISO-timestamp of when the note was last changed.
|
||||
updateUser:
|
||||
$ref: "#/components/schemas/UserInfo"
|
||||
viewCount:
|
||||
|
@ -588,7 +588,7 @@ components:
|
|||
description: How often the published version of the note was viewed.
|
||||
createTime:
|
||||
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:
|
||||
type: array
|
||||
description: List of usernames who edited the note.
|
||||
|
@ -614,20 +614,19 @@ components:
|
|||
type: boolean
|
||||
|
||||
NoteRevisionsMetadata:
|
||||
type: object
|
||||
properties:
|
||||
revision:
|
||||
type: array
|
||||
description: Array that holds all revision-info objects.
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
time:
|
||||
type: integer
|
||||
description: UNIX-timestamp of when the revision was saved. Is also the revision-id.
|
||||
length:
|
||||
type: integer
|
||||
description: Length of the document to the timepoint the revision was saved.
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: The id of the revision
|
||||
createdAt:
|
||||
type: integer
|
||||
description: ISO-timestamp of when the revision was saved. Is also the revision-id.
|
||||
length:
|
||||
type: integer
|
||||
description: Length of the document to the timepoint the revision was saved.
|
||||
NoteRevision:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"class-transformer": "^0.2.3",
|
||||
"class-validator": "^0.12.2",
|
||||
"connect-typeorm": "^1.1.4",
|
||||
"raw-body": "^2.4.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^6.5.4",
|
||||
|
|
|
@ -4,6 +4,8 @@ import { HistoryModule } from '../../../history/history.module';
|
|||
import { AuthorColor } from '../../../notes/author-color.entity';
|
||||
import { Note } from '../../../notes/note.entity';
|
||||
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 { Identity } from '../../../users/identity.entity';
|
||||
import { User } from '../../../users/user.entity';
|
||||
|
@ -28,6 +30,10 @@ describe('Me Controller', () => {
|
|||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(AuthorColor))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Authorship))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Revision))
|
||||
.useValue({})
|
||||
.compile();
|
||||
|
||||
controller = module.get<MeController>(MeController);
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
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 { NotesService } from '../../../notes/notes.service';
|
||||
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 { NotesController } from './notes.controller';
|
||||
|
||||
describe('Notes Controller', () => {
|
||||
|
@ -13,8 +18,14 @@ describe('Notes Controller', () => {
|
|||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [NotesController],
|
||||
providers: [NotesService],
|
||||
imports: [RevisionsModule],
|
||||
providers: [
|
||||
NotesService,
|
||||
{
|
||||
provide: getRepositoryToken(Note),
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
imports: [RevisionsModule, UsersModule],
|
||||
})
|
||||
.overrideProvider(getRepositoryToken(Note))
|
||||
.useValue({})
|
||||
|
@ -22,6 +33,16 @@ describe('Notes Controller', () => {
|
|||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Authorship))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(AuthorColor))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(User))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(AuthToken))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Identity))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Note))
|
||||
.useValue({})
|
||||
.compile();
|
||||
|
||||
controller = module.get<NotesController>(NotesController);
|
||||
|
|
|
@ -1,53 +1,98 @@
|
|||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Header,
|
||||
Logger,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Req,
|
||||
} from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import * as getRawBody from 'raw-body';
|
||||
import { NotePermissionsUpdateDto } from '../../../notes/note-permissions.dto';
|
||||
import { NotesService } from '../../../notes/notes.service';
|
||||
import { RevisionsService } from '../../../revisions/revisions.service';
|
||||
|
||||
@Controller('notes')
|
||||
export class NotesController {
|
||||
private readonly logger = new Logger(NotesController.name);
|
||||
|
||||
constructor(
|
||||
private noteService: NotesService,
|
||||
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()
|
||||
createNote(@Body() noteContent: string) {
|
||||
return this.noteService.createNote(noteContent);
|
||||
async createNote(@Req() req: Request) {
|
||||
// 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')
|
||||
getNote(@Param('noteIdOrAlias') noteIdOrAlias: string) {
|
||||
return this.noteService.getNoteByIdOrAlias(noteIdOrAlias);
|
||||
return this.noteService.getNoteDtoByIdOrAlias(noteIdOrAlias);
|
||||
}
|
||||
|
||||
@Post(':noteAlias')
|
||||
createNamedNote(
|
||||
async createNamedNote(
|
||||
@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')
|
||||
deleteNote(@Param('noteIdOrAlias') noteIdOrAlias: string) {
|
||||
return this.noteService.deleteNoteByIdOrAlias(noteIdOrAlias);
|
||||
async deleteNote(@Param('noteIdOrAlias') noteIdOrAlias: string) {
|
||||
this.logger.debug('Deleting note: ' + noteIdOrAlias);
|
||||
await this.noteService.deleteNoteByIdOrAlias(noteIdOrAlias);
|
||||
this.logger.debug('Successfully deleted ' + noteIdOrAlias);
|
||||
return;
|
||||
}
|
||||
|
||||
@Put(':noteIdOrAlias')
|
||||
updateNote(
|
||||
async updateNote(
|
||||
@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')
|
||||
|
@ -77,7 +122,7 @@ export class NotesController {
|
|||
@Get(':noteIdOrAlias/revisions/:revisionId')
|
||||
getNoteRevision(
|
||||
@Param('noteIdOrAlias') noteIdOrAlias: string,
|
||||
@Param('revisionId') revisionId: string,
|
||||
@Param('revisionId') revisionId: number,
|
||||
) {
|
||||
return this.revisionsService.getNoteRevision(noteIdOrAlias, revisionId);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ export class NoteDto {
|
|||
content: string;
|
||||
|
||||
@ValidateNested()
|
||||
metdata: NoteMetadataDto;
|
||||
metadata: NoteMetadataDto;
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
|
|
|
@ -16,64 +16,64 @@ import { AuthorColor } from './author-color.entity';
|
|||
export class Note {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
unique: true,
|
||||
})
|
||||
shortid: string;
|
||||
|
||||
@Column({
|
||||
unique: true,
|
||||
nullable: true,
|
||||
})
|
||||
alias: string;
|
||||
|
||||
@OneToMany(
|
||||
_ => NoteGroupPermission,
|
||||
groupPermission => groupPermission.note,
|
||||
)
|
||||
groupPermissions: NoteGroupPermission[];
|
||||
|
||||
@OneToMany(
|
||||
_ => NoteUserPermission,
|
||||
userPermission => userPermission.note,
|
||||
)
|
||||
userPermissions: NoteUserPermission[];
|
||||
|
||||
@Column({
|
||||
nullable: false,
|
||||
default: 0,
|
||||
})
|
||||
viewcount: number;
|
||||
|
||||
@ManyToOne(
|
||||
_ => User,
|
||||
user => user.ownedNotes,
|
||||
{ onDelete: 'CASCADE' },
|
||||
)
|
||||
owner: User;
|
||||
|
||||
@OneToMany(
|
||||
_ => Revision,
|
||||
revision => revision.note,
|
||||
{ cascade: true },
|
||||
)
|
||||
revisions: Revision[];
|
||||
|
||||
revisions: Promise<Revision[]>;
|
||||
@OneToMany(
|
||||
_ => AuthorColor,
|
||||
authorColor => authorColor.note,
|
||||
)
|
||||
authorColors: AuthorColor[];
|
||||
|
||||
constructor(shortid: string, alias: string, owner: User) {
|
||||
if (shortid) {
|
||||
this.shortid = shortid;
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
this.shortid = shortIdGenerate() as string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
private constructor() {}
|
||||
|
||||
public static create(owner?: User, alias?: string, shortid?: string) {
|
||||
if (!shortid) {
|
||||
shortid = shortIdGenerate();
|
||||
}
|
||||
this.alias = alias;
|
||||
this.owner = owner;
|
||||
const newNote = new Note();
|
||||
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 { RevisionsModule } from '../revisions/revisions.module';
|
||||
import { UsersModule } from '../users/users.module';
|
||||
import { AuthorColor } from './author-color.entity';
|
||||
import { Note } from './note.entity';
|
||||
import { NotesService } from './notes.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Note, AuthorColor])],
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Note, AuthorColor]),
|
||||
forwardRef(() => RevisionsModule),
|
||||
UsersModule,
|
||||
],
|
||||
controllers: [],
|
||||
providers: [NotesService],
|
||||
exports: [NotesService],
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
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';
|
||||
|
||||
describe('NotesService', () => {
|
||||
|
@ -6,9 +16,30 @@ describe('NotesService', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [NotesService],
|
||||
}).compile();
|
||||
|
||||
providers: [
|
||||
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);
|
||||
});
|
||||
|
||||
|
|
|
@ -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 {
|
||||
NotePermissionsDto,
|
||||
NotePermissionsUpdateDto,
|
||||
} from './note-permissions.dto';
|
||||
import { NoteDto } from './note.dto';
|
||||
import { Note } from './note.entity';
|
||||
import { NoteUtils } from './note.utils';
|
||||
|
||||
@Injectable()
|
||||
export class NotesService {
|
||||
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[] {
|
||||
this.logger.warn('Using hardcoded data!');
|
||||
return [
|
||||
|
@ -43,140 +58,70 @@ export class NotesService {
|
|||
];
|
||||
}
|
||||
|
||||
createNote(noteContent: string, alias?: NoteMetadataDto['alias']): NoteDto {
|
||||
this.logger.warn('Using hardcoded data!');
|
||||
return {
|
||||
content: noteContent,
|
||||
metdata: {
|
||||
alias: alias,
|
||||
createTime: new Date(),
|
||||
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: [],
|
||||
};
|
||||
async createNoteDto(
|
||||
noteContent: string,
|
||||
alias?: NoteMetadataDto['alias'],
|
||||
owner?: User,
|
||||
): Promise<NoteDto> {
|
||||
const note = await this.createNote(noteContent, alias, owner);
|
||||
return this.toNoteDto(note);
|
||||
}
|
||||
|
||||
getNoteByIdOrAlias(noteIdOrAlias: string) {
|
||||
this.logger.warn('Using hardcoded data!');
|
||||
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: [],
|
||||
};
|
||||
async createNote(
|
||||
noteContent: string,
|
||||
alias?: NoteMetadataDto['alias'],
|
||||
owner?: User,
|
||||
): Promise<Note> {
|
||||
const newNote = Note.create();
|
||||
newNote.revisions = Promise.resolve([
|
||||
//TODO: Calculate patch
|
||||
Revision.create(noteContent, noteContent),
|
||||
]);
|
||||
if (alias) {
|
||||
newNote.alias = alias;
|
||||
}
|
||||
if (owner) {
|
||||
newNote.owner = owner;
|
||||
}
|
||||
return this.noteRepository.save(newNote);
|
||||
}
|
||||
|
||||
deleteNoteByIdOrAlias(noteIdOrAlias: string) {
|
||||
this.logger.warn('Using hardcoded data!');
|
||||
return;
|
||||
async getCurrentContent(note: Note) {
|
||||
return (await this.getLastRevision(note)).content;
|
||||
}
|
||||
|
||||
updateNoteByIdOrAlias(noteIdOrAlias: string, noteContent: string) {
|
||||
this.logger.warn('Using hardcoded data!');
|
||||
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: [],
|
||||
};
|
||||
async getLastRevision(note: Note): Promise<Revision> {
|
||||
return this.revisionsService.getLatestRevision(note.id);
|
||||
}
|
||||
|
||||
getNoteMetadata(noteIdOrAlias: string): NoteMetadataDto {
|
||||
this.logger.warn('Using hardcoded data!');
|
||||
async getMetadata(note: Note): Promise<NoteMetadataDto> {
|
||||
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(),
|
||||
description: 'Very descriptive text.',
|
||||
editedBy: [],
|
||||
id: noteIdOrAlias,
|
||||
description: NoteUtils.parseDescription(note),
|
||||
editedBy: note.authorColors.map(authorColor => authorColor.user.userName),
|
||||
// TODO: Extract into method
|
||||
permission: {
|
||||
owner: {
|
||||
displayName: 'foo',
|
||||
userName: 'fooUser',
|
||||
email: 'foo@example.com',
|
||||
photo: '',
|
||||
},
|
||||
sharedToUsers: [],
|
||||
sharedToGroups: [],
|
||||
owner: this.usersService.toUserDto(note.owner),
|
||||
sharedToUsers: note.userPermissions.map(noteUserPermission => ({
|
||||
user: this.usersService.toUserDto(noteUserPermission.user),
|
||||
canEdit: noteUserPermission.canEdit,
|
||||
})),
|
||||
sharedToGroups: note.groupPermissions.map(noteGroupPermission => ({
|
||||
group: noteGroupPermission.group,
|
||||
canEdit: noteGroupPermission.canEdit,
|
||||
})),
|
||||
},
|
||||
tags: [],
|
||||
title: 'Title!',
|
||||
updateTime: new Date(),
|
||||
tags: NoteUtils.parseTags(note),
|
||||
updateTime: (await this.getLastRevision(note)).createdAt,
|
||||
// TODO: Get actual updateUser
|
||||
updateUser: {
|
||||
displayName: 'foo',
|
||||
userName: 'fooUser',
|
||||
displayName: 'Hardcoded User',
|
||||
userName: 'hardcoded',
|
||||
email: 'foo@example.com',
|
||||
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(
|
||||
noteIdOrAlias: string,
|
||||
newPermissions: NotePermissionsUpdateDto,
|
||||
|
@ -201,8 +187,16 @@ export class NotesService {
|
|||
};
|
||||
}
|
||||
|
||||
getNoteContent(noteIdOrAlias: string) {
|
||||
this.logger.warn('Using hardcoded data!');
|
||||
return '# Markdown';
|
||||
async getNoteContent(noteIdOrAlias: string): Promise<string> {
|
||||
const note = await this.getNoteByIdOrAlias(noteIdOrAlias);
|
||||
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';
|
||||
|
||||
export class RevisionMetadataDto {
|
||||
@IsString()
|
||||
@IsNumber()
|
||||
id: Revision['id'];
|
||||
|
||||
@IsDate()
|
||||
updatedAt: Date;
|
||||
createdAt: Date;
|
||||
|
||||
@IsNumber()
|
||||
length: number;
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { IsString } from 'class-validator';
|
||||
import { IsDate, IsNumber, IsString } from 'class-validator';
|
||||
import { Revision } from './revision.entity';
|
||||
|
||||
export class RevisionDto {
|
||||
@IsString()
|
||||
@IsNumber()
|
||||
id: Revision['id'];
|
||||
@IsString()
|
||||
content: string;
|
||||
@IsString()
|
||||
patch: string;
|
||||
@IsDate()
|
||||
createdAt: Date;
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ import { Authorship } from './authorship.entity';
|
|||
*/
|
||||
@Entity()
|
||||
export class Revision {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* The patch from the previous revision to this one.
|
||||
|
@ -65,4 +65,15 @@ export class Revision {
|
|||
)
|
||||
@JoinTable()
|
||||
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 { NotesModule } from '../notes/notes.module';
|
||||
import { Authorship } from './authorship.entity';
|
||||
import { Revision } from './revision.entity';
|
||||
import { RevisionsService } from './revisions.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Revision, Authorship])],
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Revision, Authorship]),
|
||||
forwardRef(() => NotesModule),
|
||||
],
|
||||
providers: [RevisionsService],
|
||||
exports: [RevisionsService],
|
||||
})
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
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';
|
||||
|
||||
describe('RevisionsService', () => {
|
||||
|
@ -6,8 +15,30 @@ describe('RevisionsService', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [RevisionsService],
|
||||
}).compile();
|
||||
providers: [
|
||||
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);
|
||||
});
|
||||
|
|
|
@ -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 { RevisionDto } from './revision.dto';
|
||||
import { Revision } from './revision.entity';
|
||||
|
||||
@Injectable()
|
||||
export class RevisionsService {
|
||||
private readonly logger = new Logger(RevisionsService.name);
|
||||
getNoteRevisionMetadatas(noteIdOrAlias: string): RevisionMetadataDto[] {
|
||||
this.logger.warn('Using hardcoded data!');
|
||||
return [
|
||||
{
|
||||
id: 'some-uuid',
|
||||
updatedAt: new Date(),
|
||||
length: 42,
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Revision)
|
||||
private revisionRepository: Repository<Revision>,
|
||||
@Inject(forwardRef(() => NotesService)) private notesService: NotesService,
|
||||
) {}
|
||||
|
||||
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 {
|
||||
this.logger.warn('Using hardcoded data!');
|
||||
async getNoteRevision(
|
||||
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 {
|
||||
id: revisionId,
|
||||
content: 'Foobar',
|
||||
patch: 'barfoo',
|
||||
id: revision.id,
|
||||
length: revision.length,
|
||||
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 { UserInfoDto } from './user-info.dto';
|
||||
import { User } from './user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
|
@ -15,4 +16,26 @@ export class UsersService {
|
|||
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 { Test } from '@nestjs/testing';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
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 { PermissionsModule } from '../../src/permissions/permissions.module';
|
||||
|
||||
describe('Notes', () => {
|
||||
let app: INestApplication;
|
||||
|
@ -10,29 +14,44 @@ describe('Notes', () => {
|
|||
|
||||
beforeAll(async () => {
|
||||
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();
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
notesService = moduleRef.get(NotesService);
|
||||
await app.init();
|
||||
notesService = moduleRef.get(NotesService);
|
||||
const noteRepository = moduleRef.get('NoteRepository');
|
||||
noteRepository.clear();
|
||||
});
|
||||
|
||||
it(`POST /notes`, async () => {
|
||||
const newNote = 'This is a test note.';
|
||||
const response = await request(app.getHttpServer())
|
||||
.post('/notes')
|
||||
.set('Content-Type', 'text/markdown')
|
||||
.send(newNote)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(201);
|
||||
expect(response.body.metadata?.id).toBeDefined();
|
||||
expect(
|
||||
notesService.getNoteByIdOrAlias(response.body.metadata.id).content,
|
||||
(await notesService.getNoteDtoByIdOrAlias(response.body.metadata.id))
|
||||
.content,
|
||||
).toEqual(newNote);
|
||||
});
|
||||
|
||||
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())
|
||||
.get('/notes/test1')
|
||||
.expect('Content-Type', /json/)
|
||||
|
@ -44,38 +63,44 @@ describe('Notes', () => {
|
|||
const newNote = 'This is a test note.';
|
||||
const response = await request(app.getHttpServer())
|
||||
.post('/notes/test2')
|
||||
.set('Content-Type', 'text/markdown')
|
||||
.send(newNote)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(201);
|
||||
expect(response.body.metadata?.id).toBeDefined();
|
||||
return expect(
|
||||
notesService.getNoteByIdOrAlias(response.body.metadata.id).content,
|
||||
(await notesService.getNoteDtoByIdOrAlias(response.body.metadata.id))
|
||||
.content,
|
||||
).toEqual(newNote);
|
||||
});
|
||||
|
||||
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())
|
||||
.delete('/notes/test3')
|
||||
.expect(200);
|
||||
return expect(notesService.getNoteByIdOrAlias('test3')).toBeNull();
|
||||
return expect(notesService.getNoteByIdOrAlias('test3')).rejects.toEqual(
|
||||
Error('Note not found'),
|
||||
);
|
||||
});
|
||||
|
||||
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())
|
||||
.put('/notes/test4')
|
||||
.set('Content-Type', 'text/markdown')
|
||||
.send('New note text')
|
||||
.expect(200);
|
||||
return expect(notesService.getNoteByIdOrAlias('test4').content).toEqual(
|
||||
'New note text',
|
||||
);
|
||||
return expect(
|
||||
(await notesService.getNoteDtoByIdOrAlias('test4')).content,
|
||||
).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);
|
||||
});
|
||||
|
||||
|
@ -88,29 +113,30 @@ describe('Notes', () => {
|
|||
});
|
||||
|
||||
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())
|
||||
.get('/notes/test7/revisions')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
expect(response.body.revisions).toHaveLength(1);
|
||||
expect(response.body).toHaveLength(1);
|
||||
});
|
||||
|
||||
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())
|
||||
.get('/notes/test8/revisions/1')
|
||||
.get('/notes/test8/revisions/' + revision.id)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
expect(response.body.content).toEqual('This is a test note.');
|
||||
});
|
||||
|
||||
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())
|
||||
.get('/notes/test9/content')
|
||||
.expect(200);
|
||||
expect(response.body).toEqual('This is a test note.');
|
||||
expect(response.text).toEqual('This is a test note.');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -3622,7 +3622,7 @@ http-errors@1.7.2:
|
|||
statuses ">= 1.5.0 < 2"
|
||||
toidentifier "1.0.0"
|
||||
|
||||
http-errors@~1.7.2:
|
||||
http-errors@1.7.3, http-errors@~1.7.2:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
|
||||
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
|
||||
|
@ -5944,6 +5944,16 @@ raw-body@2.4.0:
|
|||
iconv-lite "0.4.24"
|
||||
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:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||
|
|
Loading…
Add table
Reference in a new issue