Merge branch 'develop' into public-api-uploads

This commit is contained in:
Yannick Bungers 2020-10-30 22:46:08 +01:00 committed by GitHub
commit 8a6e81e1c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 919 additions and 683 deletions

View file

@ -1,17 +1,16 @@
@startuml @startuml
' hide the spot
hide circle hide circle
skinparam nodesep 60
' avoid problems with angled crows feet
skinparam linetype ortho
entity "Note" { entity "Note" {
*id : uuid <<generated>> *id : uuid <<generated>>
-- --
*shortid : text *shortid : text
*alias : text alias : text
*viewcount : number *viewcount : number
*ownerId : uuid <<FK User>> *ownerId : uuid <<FK User>>
description: text
title: text
} }
entity "User" { entity "User" {
@ -49,7 +48,7 @@ entity "Identity" {
passwordHash : text passwordHash : text
} }
entity "Session" as seesion { entity "Session" {
*id : text *id : text
-- --
*expiredAt : number *expiredAt : number
@ -108,13 +107,18 @@ entity "Group" {
*special : boolean *special : boolean
} }
entity "NoteGroupPermission" { entity "NoteGroupPermission" {
*groupId : number <<FK Group>> *groupId : number <<FK Group>>
*noteId : uuid <<FK Note>> *noteId : uuid <<FK Note>>
-- --
*canEdit : boolean *canEdit : boolean
} }
entity "Tag" {
*id: number <<generated>>
*name: text
}
entity "MediaUpload" { entity "MediaUpload" {
*id : text <<unique>> *id : text <<unique>>
-- --
@ -125,20 +129,26 @@ entity "MediaUpload" {
*createdAt : date *createdAt : date
} }
Note "1" - "1..*" Revision User "1" -- "0..*" Note: owner
User "1" -u- "1..*" Identity
User "1" - "1..*" authToken
User "1" -l- "1..*" Session
User "1" - "0..*" MediaUpload
User "0..*" -- "0..*" Note
User "1" - "0..*" Authorship
(User, Note) . AuthorColors
Revision "0..*" - "0..*" Authorship Revision "0..*" - "0..*" Authorship
(Revision, Authorship) .. RevisionAuthorship (Revision, Authorship) .. RevisionAuthorship
Authorship "0..*" -- "1" User
Note "0..*" -- "1" User : owner MediaUpload "0..*" -- "1" Note
Note "1" -- "0..*" NoteUserPermission Note "1" - "1..*" Revision
NoteUserPermission "1" -- "1" User Note "0..*" -l- "0..*" Tag
Note "1" -- "0..*" NoteGroupPermission Note "0..*" -- "0..*" Group
NoteGroupPermission "0..*" -- "1" Group
Identity "1..*" -- "1" User User "0..*" -- "0..*" Note
authToken "1..*" -- "1" User (User, Note) . NoteUserPermission
seesion "1..*" -- "1" User (Note, Group) . NoteGroupPermission
Note "0..*" -- "0..*" User : color
(Note, User) .. AuthorColors
MediaUpload "0..*" -- "1" Note
MediaUpload "0..*" -- "1" User
@enduml @enduml

View file

@ -47,7 +47,9 @@ paths:
content: content:
application/json: application/json:
schema: schema:
"$ref": "#/components/schemas/History" type: array
items:
"$ref": "#/components/schemas/History"
'401': '401':
"$ref": "#/components/responses/UnauthorizedError" "$ref": "#/components/responses/UnauthorizedError"
/me/history/{note}: /me/history/{note}:
@ -60,11 +62,11 @@ paths:
description: JSON Object which contains id, title, tags, last visit time and pinned status description: JSON Object which contains id, title, tags, last visit time and pinned status
responses: responses:
'200': '200':
description: The list of recently viewed notes and pinned notes. description: Information about the history entry
content: content:
application/json: application/json:
schema: schema:
"$ref": "#/components/schemas/HistoryObject" "$ref": "#/components/schemas/History"
'401': '401':
"$ref": "#/components/responses/UnauthorizedError" "$ref": "#/components/responses/UnauthorizedError"
'404': '404':
@ -88,14 +90,14 @@ paths:
content: content:
application/json: application/json:
schema: schema:
"$ref": "#/components/schemas/HistoryObject" "$ref": "#/components/schemas/HistoryUpdate"
responses: responses:
'200': '200':
description: The new history. description: The new history object.
content: content:
application/json: application/json:
schema: schema:
"$ref": "#/components/schemas/HistoryObject" "$ref": "#/components/schemas/History"
'401': '401':
"$ref": "#/components/responses/UnauthorizedError" "$ref": "#/components/responses/UnauthorizedError"
'404': '404':
@ -183,7 +185,7 @@ paths:
markdownExample: markdownExample:
"$ref": '#/components/examples/markdownExample' "$ref": '#/components/examples/markdownExample'
responses: responses:
'200': '201':
description: Get information about the newly created note. description: Get information about the newly created note.
content: content:
application/json: application/json:
@ -226,7 +228,7 @@ paths:
- note - note
summary: Imports some markdown data into a new note with a given alias summary: Imports some markdown data into a new note with a given alias
operationId: createNoteWithAlias operationId: createNoteWithAlias
description: This endpoint equals to the above one except that the alias from the url will be assigned to the note if [FreeURL-mode](https://github.com/codimd/server/tree/master/docs/configuration-env-vars.md#users-and-privileges) is enabled. description: This endpoint creates a new note with the content of the HTTP request body and the alias from the URL parameter.
requestBody: requestBody:
required: true required: true
description: The content of the note to be imported as markdown. description: The content of the note to be imported as markdown.
@ -238,7 +240,7 @@ paths:
markdownExample: markdownExample:
"$ref": '#/components/examples/markdownExample' "$ref": '#/components/examples/markdownExample'
responses: responses:
'200': '201':
description: Get information about the newly created note. description: Get information about the newly created note.
content: content:
application/json: application/json:
@ -285,7 +287,7 @@ paths:
- note - note
summary: Imports some markdown data into an existing note, creating a new revision summary: Imports some markdown data into an existing note, creating a new revision
operationId: createNewRevisionForNote operationId: createNewRevisionForNote
description: This endpoint equals to the above one except that the alias from the url will be assigned to the note if [FreeURL-mode](https://github.com/codimd/server/tree/master/docs/configuration-env-vars.md#users-and-privileges) is enabled. description: This endpoint updates the note content of an existing note. The old content is completely replaced and a new revision is created.
requestBody: requestBody:
required: true required: true
description: The content of the note to be imported as markdown. description: The content of the note to be imported as markdown.
@ -318,20 +320,14 @@ paths:
text/plain: text/plain:
example: my-note example: my-note
/notes/{note}/metadata: /notes/{note}/metadata:
put: get:
tags: tags:
- note - note
summary: Set the permissions of a note summary: Get the metadata of a note
operationId: updateNoteMetadata operationId: getNoteMetadata
requestBody:
required: true
content:
application/json:
schema:
"$ref": "#/components/schemas/NoteMetadata"
responses: responses:
'200': '200':
description: The updated permissions of the note. description: The metadata of the note.
content: content:
application/json: application/json:
schema: schema:
@ -350,18 +346,24 @@ paths:
content: content:
text/plain: text/plain:
example: my-note example: my-note
get: /notes/{note}/permissions:
tags: put:
- note tags: [ note ]
summary: Get the permissions of a note summary: Set permissions of a note
operationId: getNoteMetadata operationId: updateNotePermissions
requestBody:
required: true
content:
application/json:
schema:
"$ref": "#/components/schemas/NotePermissionsUpdate"
responses: responses:
'200': '200':
description: The permissions of the note. description: The updated permissions of the note.
content: content:
application/json: application/json:
schema: schema:
"$ref": "#/components/schemas/NoteMetadata" "$ref": "#/components/schemas/NotePermissions"
'401': '401':
"$ref": "#/components/responses/UnauthorizedError" "$ref": "#/components/responses/UnauthorizedError"
'403': '403':
@ -389,7 +391,9 @@ paths:
content: content:
application/json: application/json:
schema: schema:
"$ref": "#/components/schemas/NoteRevisionsMetadata" type: array
items:
"$ref": "#/components/schemas/NoteRevisionsMetadata"
'401': '401':
"$ref": "#/components/responses/UnauthorizedError" "$ref": "#/components/responses/UnauthorizedError"
'403': '403':
@ -552,7 +556,7 @@ paths:
required: true required: true
description: ID or alias of the parent note description: ID or alias of the parent note
responses: responses:
'200': '201':
description: The file was uploaded successfully. description: The file was uploaded successfully.
content: content:
application/json: application/json:
@ -642,6 +646,15 @@ components:
properties: properties:
password: password:
type: string type: string
GroupInfo:
type: object
properties:
name:
type: string
displayName:
type: string
special:
type: boolean
ImageProxyRequest: ImageProxyRequest:
type: object type: object
properties: properties:
@ -713,9 +726,32 @@ components:
type: object type: object
properties: properties:
owner: owner:
type: string $ref: "#/components/schemas/UserInfo"
description: Username of the owner of the note sharedToUsers:
sharedTo: type: array
description: Contains all users that can read the note and a boolean that denotes if they can also edit.
items:
type: object
properties:
user:
$ref: "#/components/schemas/UserInfo"
canEdit:
type: boolean
sharedToGroups:
type: array
description: Contains all groups that can read the note and a boolean that denotes if they can also edit.
items:
type: object
properties:
group:
$ref: "#/components/schemas/GroupInfo"
canEdit:
type: boolean
NotePermissionsUpdate:
type: object
description: Contains only title, description and tags of a note.
properties:
sharedToUsers:
type: array type: array
description: Contains all usernames that can read the note and a boolean that denotes if they can also edit. description: Contains all usernames that can read the note and a boolean that denotes if they can also edit.
items: items:
@ -725,21 +761,28 @@ components:
type: string type: string
canEdit: canEdit:
type: boolean type: boolean
sharedToGroups:
type: array
description: Contains all groups that can read the note and a boolean that denotes if they can also edit.
items:
type: object
properties:
groupname:
type: string
canEdit:
type: boolean
NoteRevisionsMetadata: NoteRevisionsMetadata:
type: array type: object
items: properties:
type: object id:
properties: type: integer
id: description: The id of the revision
type: integer createdTime:
description: The id of the revision type: string
createdTime: description: ISO-timestamp of when the revision was saved. Is also the revision-id.
type: string length:
description: ISO-timestamp of when the revision was saved. Is also the revision-id. type: integer
length: description: Length of the document to the timepoint the revision was saved.
type: integer
description: Length of the document to the timepoint the revision was saved.
NoteRevision: NoteRevision:
type: object type: object
properties: properties:
@ -840,7 +883,7 @@ components:
type: boolean type: boolean
disconnectSocketQueueLength: disconnectSocketQueueLength:
type: integer type: integer
HistoryObject: History:
type: object type: object
properties: properties:
metadata: metadata:
@ -848,14 +891,12 @@ components:
pinned: pinned:
type: boolean type: boolean
description: Whether the user has pinned this note. description: Whether the user has pinned this note.
History: HistoryUpdate:
type: object type: object
properties: properties:
history: pinned:
type: array type: boolean
description: The array that contains history objects. description: Whether the user has pinned this note.
items:
"$ref": "#/components/schemas/HistoryObject"
MediaUpload: MediaUpload:
type: object type: object
properties: properties:

View file

@ -5,6 +5,7 @@ import { LoggerModule } from '../../../logger/logger.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 { Tag } from '../../../notes/tag.entity';
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 { AuthToken } from '../../../users/auth-token.entity'; import { AuthToken } from '../../../users/auth-token.entity';
@ -35,6 +36,8 @@ describe('Me Controller', () => {
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(Revision)) .overrideProvider(getRepositoryToken(Revision))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(Tag))
.useValue({})
.compile(); .compile();
controller = module.get<MeController>(MeController); controller = module.get<MeController>(MeController);

View file

@ -6,6 +6,7 @@ import { MediaModule } from '../../../media/media.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 { Tag } from '../../../notes/tag.entity';
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 { AuthToken } from '../../../users/auth-token.entity'; import { AuthToken } from '../../../users/auth-token.entity';
@ -37,6 +38,8 @@ describe('Media Controller', () => {
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(User)) .overrideProvider(getRepositoryToken(User))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(Tag))
.useValue({})
.compile(); .compile();
controller = module.get<MediaController>(MediaController); controller = module.get<MediaController>(MediaController);

View file

@ -4,6 +4,7 @@ import { LoggerModule } from '../../../logger/logger.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 { NotesService } from '../../../notes/notes.service'; import { NotesService } from '../../../notes/notes.service';
import { Tag } from '../../../notes/tag.entity';
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';
@ -25,6 +26,10 @@ describe('Notes Controller', () => {
provide: getRepositoryToken(Note), provide: getRepositoryToken(Note),
useValue: {}, useValue: {},
}, },
{
provide: getRepositoryToken(Tag),
useValue: {},
},
], ],
imports: [RevisionsModule, UsersModule, LoggerModule], imports: [RevisionsModule, UsersModule, LoggerModule],
}) })
@ -44,6 +49,8 @@ describe('Notes Controller', () => {
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(Note)) .overrideProvider(getRepositoryToken(Note))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(Tag))
.useValue({})
.compile(); .compile();
controller = module.get<NotesController>(NotesController); controller = module.get<NotesController>(NotesController);

View file

@ -9,6 +9,7 @@ import {
Put, Put,
} from '@nestjs/common'; } from '@nestjs/common';
import { ConsoleLoggerService } from '../../../logger/console-logger.service'; import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { NoteMetadataUpdateDto } from '../../../notes/note-metadata.dto';
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';
@ -21,7 +22,7 @@ export class NotesController {
private noteService: NotesService, private noteService: NotesService,
private revisionsService: RevisionsService, private revisionsService: RevisionsService,
) { ) {
this.logger.setContext(NotesController.name); this.logger.setContext(NotesController.name);
} }
@Post() @Post()

View file

@ -20,7 +20,7 @@ export class HistoryService {
description: 'Very descriptive text.', description: 'Very descriptive text.',
editedBy: [], editedBy: [],
id: 'foobar-barfoo', id: 'foobar-barfoo',
permission: { permissions: {
owner: { owner: {
displayName: 'foo', displayName: 'foo',
userName: 'fooUser', userName: 'fooUser',
@ -59,7 +59,7 @@ export class HistoryService {
description: 'Very descriptive text.', description: 'Very descriptive text.',
editedBy: [], editedBy: [],
id: 'foobar-barfoo', id: 'foobar-barfoo',
permission: { permissions: {
owner: { owner: {
displayName: 'foo', displayName: 'foo',
userName: 'fooUser', userName: 'fooUser',

View file

@ -4,6 +4,7 @@ import { LoggerModule } from '../logger/logger.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 { Tag } from '../notes/tag.entity';
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 { AuthToken } from '../users/auth-token.entity'; import { AuthToken } from '../users/auth-token.entity';
@ -43,6 +44,8 @@ describe('MediaService', () => {
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(User)) .overrideProvider(getRepositoryToken(User))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(Tag))
.useValue({})
.compile(); .compile();
service = module.get<MediaService>(MediaService); service = module.get<MediaService>(MediaService);

View file

@ -32,5 +32,15 @@ export class NoteMetadataDto {
@ValidateNested() @ValidateNested()
editedBy: UserInfoDto['userName'][]; editedBy: UserInfoDto['userName'][];
@ValidateNested() @ValidateNested()
permission: NotePermissionsDto; permissions: NotePermissionsDto;
}
export class NoteMetadataUpdateDto {
@IsString()
title: string;
@IsString()
description: string;
@IsArray()
@IsString({ each: true })
tags: string[];
} }

View file

@ -8,7 +8,7 @@ export class NoteUserPermissionEntryDto {
canEdit: boolean; canEdit: boolean;
} }
export class NotePermissionEntryUpdateDto { export class NoteUserPermissionUpdateDto {
@IsString() @IsString()
username: string; username: string;
@IsBoolean() @IsBoolean()
@ -31,6 +31,13 @@ export class NoteGroupPermissionEntryDto {
canEdit: boolean; canEdit: boolean;
} }
export class NoteGroupPermissionUpdateDto {
@IsString()
groupname: string;
@IsBoolean()
canEdit: boolean;
}
export class NotePermissionsDto { export class NotePermissionsDto {
@ValidateNested() @ValidateNested()
owner: UserInfoDto; owner: UserInfoDto;
@ -45,5 +52,8 @@ export class NotePermissionsDto {
export class NotePermissionsUpdateDto { export class NotePermissionsUpdateDto {
@IsArray() @IsArray()
@ValidateNested() @ValidateNested()
sharedTo: NotePermissionEntryUpdateDto[]; sharedToUsers: NoteUserPermissionUpdateDto[];
@IsArray()
@ValidateNested()
sharedToGroups: NoteGroupPermissionUpdateDto[];
} }

View file

@ -2,6 +2,8 @@ import { generate as shortIdGenerate } from 'shortid';
import { import {
Column, Column,
Entity, Entity,
JoinTable,
ManyToMany,
ManyToOne, ManyToOne,
OneToMany, OneToMany,
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
@ -11,6 +13,7 @@ import { NoteUserPermission } from '../permissions/note-user-permission.entity';
import { Revision } from '../revisions/revision.entity'; import { Revision } from '../revisions/revision.entity';
import { User } from '../users/user.entity'; import { User } from '../users/user.entity';
import { AuthorColor } from './author-color.entity'; import { AuthorColor } from './author-color.entity';
import { Tag } from './tag.entity';
@Entity('Notes') @Entity('Notes')
export class Note { export class Note {
@ -25,7 +28,7 @@ export class Note {
unique: true, unique: true,
nullable: true, nullable: true,
}) })
alias: string; alias?: string;
@OneToMany( @OneToMany(
_ => NoteGroupPermission, _ => NoteGroupPermission,
groupPermission => groupPermission.note, groupPermission => groupPermission.note,
@ -59,10 +62,27 @@ export class Note {
) )
authorColors: AuthorColor[]; authorColors: AuthorColor[];
@Column({
nullable: true,
})
description?: string;
@Column({
nullable: true,
})
title?: string;
@ManyToMany(
_ => Tag,
tag => tag.notes,
{ eager: true, cascade: true },
)
@JoinTable()
tags: Tag[];
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {} private constructor() {}
public static create(owner?: User, alias?: string, shortid?: string) { public static create(owner?: User, alias?: string, shortid?: string): Note {
if (!shortid) { if (!shortid) {
shortid = shortIdGenerate(); shortid = shortIdGenerate();
} }
@ -74,6 +94,10 @@ export class Note {
newNote.authorColors = []; newNote.authorColors = [];
newNote.userPermissions = []; newNote.userPermissions = [];
newNote.groupPermissions = []; newNote.groupPermissions = [];
newNote.revisions = Promise.resolve([]);
newNote.description = null;
newNote.title = null;
newNote.tags = [];
return newNote; return newNote;
} }
} }

View file

@ -1,18 +0,0 @@
import { Note } from './note.entity';
export class NoteUtils {
public static parseTitle(note: Note): string {
// TODO: Implement method
return 'Hardcoded note title';
}
public static parseDescription(note: Note): string {
// TODO: Implement method
return 'Hardcoded note description';
}
public static parseTags(note: Note): string[] {
// TODO: Implement method
return ['Hardcoded note tag'];
}
}

View file

@ -6,10 +6,11 @@ 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';
import { Tag } from './tag.entity';
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([Note, AuthorColor]), TypeOrmModule.forFeature([Note, AuthorColor, Tag]),
forwardRef(() => RevisionsModule), forwardRef(() => RevisionsModule),
UsersModule, UsersModule,
LoggerModule, LoggerModule,

View file

@ -11,6 +11,7 @@ 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';
import { Tag } from './tag.entity';
describe('NotesService', () => { describe('NotesService', () => {
let service: NotesService; let service: NotesService;
@ -23,6 +24,10 @@ describe('NotesService', () => {
provide: getRepositoryToken(Note), provide: getRepositoryToken(Note),
useValue: {}, useValue: {},
}, },
{
provide: getRepositoryToken(Tag),
useValue: {},
},
], ],
imports: [UsersModule, RevisionsModule, LoggerModule], imports: [UsersModule, RevisionsModule, LoggerModule],
}) })
@ -40,6 +45,8 @@ describe('NotesService', () => {
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(Note)) .overrideProvider(getRepositoryToken(Note))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(Tag))
.useValue({})
.compile(); .compile();
service = module.get<NotesService>(NotesService); service = module.get<NotesService>(NotesService);
}); });

View file

@ -7,20 +7,21 @@ import { Revision } from '../revisions/revision.entity';
import { RevisionsService } from '../revisions/revisions.service'; import { RevisionsService } from '../revisions/revisions.service';
import { User } from '../users/user.entity'; import { User } from '../users/user.entity';
import { UsersService } from '../users/users.service'; import { UsersService } from '../users/users.service';
import { NoteMetadataDto } from './note-metadata.dto'; import { NoteMetadataDto, NoteMetadataUpdateDto } 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 { Note } from './note.entity';
import { NoteUtils } from './note.utils'; import { Tag } from './tag.entity';
@Injectable() @Injectable()
export class NotesService { export class NotesService {
constructor( constructor(
private readonly logger: ConsoleLoggerService, private readonly logger: ConsoleLoggerService,
@InjectRepository(Note) private noteRepository: Repository<Note>, @InjectRepository(Note) private noteRepository: Repository<Note>,
@InjectRepository(Tag) private tagRepository: Repository<Tag>,
@Inject(UsersService) private usersService: UsersService, @Inject(UsersService) private usersService: UsersService,
@Inject(forwardRef(() => RevisionsService)) @Inject(forwardRef(() => RevisionsService))
private revisionsService: RevisionsService, private revisionsService: RevisionsService,
@ -37,7 +38,7 @@ export class NotesService {
description: 'Very descriptive text.', description: 'Very descriptive text.',
editedBy: [], editedBy: [],
id: 'foobar-barfoo', id: 'foobar-barfoo',
permission: { permissions: {
owner: { owner: {
displayName: 'foo', displayName: 'foo',
userName: 'fooUser', userName: 'fooUser',
@ -102,13 +103,13 @@ export class NotesService {
// TODO: Convert DB UUID to base64 // TODO: Convert DB UUID to base64
id: note.id, id: note.id,
alias: note.alias, alias: note.alias,
title: NoteUtils.parseTitle(note), title: note.title,
// TODO: Get actual createTime // TODO: Get actual createTime
createTime: new Date(), createTime: new Date(),
description: NoteUtils.parseDescription(note), description: note.description,
editedBy: note.authorColors.map(authorColor => authorColor.user.userName), editedBy: note.authorColors.map(authorColor => authorColor.user.userName),
// TODO: Extract into method // TODO: Extract into method
permission: { permissions: {
owner: this.usersService.toUserDto(note.owner), owner: this.usersService.toUserDto(note.owner),
sharedToUsers: note.userPermissions.map(noteUserPermission => ({ sharedToUsers: note.userPermissions.map(noteUserPermission => ({
user: this.usersService.toUserDto(noteUserPermission.user), user: this.usersService.toUserDto(noteUserPermission.user),
@ -119,7 +120,7 @@ export class NotesService {
canEdit: noteGroupPermission.canEdit, canEdit: noteGroupPermission.canEdit,
})), })),
}, },
tags: NoteUtils.parseTags(note), tags: note.tags.map(tag => tag.name),
updateTime: (await this.getLastRevision(note)).createdAt, updateTime: (await this.getLastRevision(note)).createdAt,
// TODO: Get actual updateUser // TODO: Get actual updateUser
updateUser: { updateUser: {

19
src/notes/tag.entity.ts Normal file
View file

@ -0,0 +1,19 @@
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
import { Note } from './note.entity';
@Entity()
export class Tag {
@PrimaryGeneratedColumn()
id: number;
@Column({
nullable: false,
})
name: string;
@ManyToMany(
_ => Note,
note => note.tags,
)
notes: Note[];
}

View file

@ -10,6 +10,7 @@ import { User } from '../users/user.entity';
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';
import { Tag } from '../notes/tag.entity';
describe('RevisionsService', () => { describe('RevisionsService', () => {
let service: RevisionsService; let service: RevisionsService;
@ -39,6 +40,8 @@ describe('RevisionsService', () => {
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(Revision)) .overrideProvider(getRepositoryToken(Revision))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(Tag))
.useValue({})
.compile(); .compile();
service = module.get<RevisionsService>(RevisionsService); service = module.get<RevisionsService>(RevisionsService);

View file

@ -76,6 +76,7 @@ export class RevisionsService {
createRevision(content: string) { createRevision(content: string) {
// TODO: Add previous revision // TODO: Add previous revision
// TODO: Calculate patch // TODO: Calculate patch
// TODO: Save metadata
return this.revisionRepository.create({ return this.revisionRepository.create({
content: content, content: content,
length: content.length, length: content.length,

View file

@ -98,20 +98,28 @@ describe('Notes', () => {
).toEqual('New note text'); ).toEqual('New note text');
}); });
it.skip(`PUT /notes/{note}/metadata`, () => { it(`GET /notes/{note}/metadata`, async () => {
// TODO await notesService.createNote('This is a test note.', 'test6');
return request(app.getHttpServer()) const metadata = await request(app.getHttpServer())
.post('/notes/test5/metadata')
.set('Content-Type', 'text/markdown')
.expect(200);
});
it.skip(`GET /notes/{note}/metadata`, () => {
notesService.createNote('This is a test note.', 'test6');
return request(app.getHttpServer())
.get('/notes/test6/metadata') .get('/notes/test6/metadata')
.expect(200); .expect(200);
// TODO: Find out how to check the structure of the returned JSON expect(typeof metadata.body.id).toEqual('string');
expect(metadata.body.alias).toEqual('test6');
expect(metadata.body.title).toBeNull();
expect(metadata.body.description).toBeNull();
expect(typeof metadata.body.createTime).toEqual('string');
expect(metadata.body.editedBy).toEqual([]);
expect(metadata.body.permissions.owner).toBeNull();
expect(metadata.body.permissions.sharedToUsers).toEqual([]);
expect(metadata.body.permissions.sharedToUsers).toEqual([]);
expect(metadata.body.tags).toEqual([]);
expect(typeof metadata.body.updateTime).toEqual('string');
expect(typeof metadata.body.updateUser.displayName).toEqual('string');
expect(typeof metadata.body.updateUser.userName).toEqual('string');
expect(typeof metadata.body.updateUser.email).toEqual('string');
expect(typeof metadata.body.updateUser.photo).toEqual('string');
expect(typeof metadata.body.viewCount).toEqual('number');
expect(metadata.body.editedBy).toEqual([]);
}); });
it(`GET /notes/{note}/revisions`, async () => { it(`GET /notes/{note}/revisions`, async () => {

1228
yarn.lock

File diff suppressed because it is too large Load diff