Skip to content

Commit

Permalink
feat(note): Add tools to GET /note/id and GET /note/resolve-hostname/…
Browse files Browse the repository at this point in the history
…hostname (#248)

* Add tools to GET /note/id

* Add tools to GET note/resolve-hostname/:hostname

* Remove only

* Update src/domain/entities/note.ts

Co-authored-by: Peter Savchenko <[email protected]>

* Lint

---------

Co-authored-by: Peter Savchenko <[email protected]>
  • Loading branch information
TatianaFomina and neSpecc authored Apr 17, 2024
1 parent 50c0330 commit 51ac441
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 12 deletions.
9 changes: 8 additions & 1 deletion src/domain/entities/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,14 @@ export interface Note {
/**
* Note content
*/
content: JSON;
content: {
blocks: Array<{
id: string;
type: string;
data: unknown;
tunes?: {[name: string]: unknown}
}>
};

/**
* Note creator id
Expand Down
9 changes: 9 additions & 0 deletions src/domain/service/editorTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ export default class EditorToolsService implements EditorToolsServiceSharedMetho
return await this.repository.getToolsByIds(editorToolIds);
}

/**
* Returns bunch of editor tools by their names
*
* @param editorToolNames - tool names
*/
public async getToolsByNames(editorToolNames: EditorTool['name'][]): Promise<EditorTool[]> {
return await this.repository.getToolsByNames(editorToolNames);
}

/**
* Get tool by it's identifier
*
Expand Down
2 changes: 1 addition & 1 deletion src/domain/service/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default class NoteService {
* @param parentPublicId - parent note if exist
* @returns { Note } added note object
*/
public async addNote(content: JSON, creatorId: Note['creatorId'], parentPublicId: Note['publicId'] | undefined): Promise<Note> {
public async addNote(content: Note['content'], creatorId: Note['creatorId'], parentPublicId: Note['publicId'] | undefined): Promise<Note> {
const note = await this.noteRepository.addNote({
publicId: createPublicId(),
content,
Expand Down
3 changes: 2 additions & 1 deletion src/presentation/http/http-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ export default class HttpApi implements Api {
noteService: domainServices.noteService,
noteSettingsService: domainServices.noteSettingsService,
noteVisitsService: domainServices.noteVisitsService,
editorToolsService: domainServices.editorToolsService,
});

await this.server?.register(NoteListRouter, {
Expand Down Expand Up @@ -371,4 +372,4 @@ export default class HttpApi implements Api {
throw error;
});
}
}
}
28 changes: 28 additions & 0 deletions src/presentation/http/router/note.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ describe('Note API', () => {
accessRights: {
canEdit: false,
},
tools: [
{
name: 'header',
source: {
cdn: 'https://cdn.jsdelivr.net/npm/@editorjs/[email protected]/dist/header.umd.min.js',
},
},
{
name: 'paragraph',
source: {
cdn: 'https://cdn.jsdelivr.net/npm/@editorjs/[email protected]/dist/paragraph.umd.min.js',
},
},
],
});
});

Expand Down Expand Up @@ -151,6 +165,20 @@ describe('Note API', () => {
'accessRights': {
'canEdit': canEdit,
},
tools: [
{
name: 'header',
source: {
cdn: 'https://cdn.jsdelivr.net/npm/@editorjs/[email protected]/dist/header.umd.min.js',
},
},
{
name: 'paragraph',
source: {
cdn: 'https://cdn.jsdelivr.net/npm/@editorjs/[email protected]/dist/paragraph.umd.min.js',
},
},
],
});
} else {
expect(response?.json()).toStrictEqual({
Expand Down
55 changes: 50 additions & 5 deletions src/presentation/http/router/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import useMemberRoleResolver from '../middlewares/noteSettings/useMemberRoleReso
import { MemberRole } from '@domain/entities/team.js';
import { type NotePublic, definePublicNote } from '@domain/entities/notePublic.js';
import type NoteVisitsService from '@domain/service/noteVisits.js';
import type EditorToolsService from '@domain/service/editorTools.js';
import type EditorTool from '@domain/entities/editorTools.js';

/**
* Interface for the note router.
Expand All @@ -28,6 +30,11 @@ interface NoteRouterOptions {
* Note visits service instance
*/
noteVisitsService: NoteVisitsService;

/**
* Editor tools service instance
*/
editorToolsService: EditorToolsService;
}

/**
Expand All @@ -44,6 +51,7 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
const noteService = opts.noteService;
const noteSettingsService = opts.noteSettingsService;
const noteVisitsService = opts.noteVisitsService;
const editorToolsService = opts.editorToolsService;

/**
* Prepare note id resolver middleware
Expand Down Expand Up @@ -76,6 +84,7 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
accessRights: {
canEdit: boolean,
},
tools: EditorTool[],
}| ErrorResponse,
}>('/:notePublicId', {
config: {
Expand Down Expand Up @@ -107,6 +116,12 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
parentNote: {
$ref: 'NoteSchema',
},
tools: {
type: 'array',
items: {
$ref: 'EditorToolSchema',
},
},
},
},
},
Expand Down Expand Up @@ -139,23 +154,34 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
}

const parentId = await noteService.getParentNoteIdByNoteId(note.id);

const parentNote = parentId !== null ? definePublicNote(await noteService.getNoteById(parentId)) : undefined;

/**
* Wrap note for public use
*/
const notePublic = definePublicNote(note);

/**
* Get all tools used in the note
*/
const noteToolsNames = new Set<string>();

note.content.blocks.forEach((block: { type: string }) => {
noteToolsNames.add(block.type);
});

const noteTools = await editorToolsService.getToolsByNames(Array.from(noteToolsNames));

/**
* Check if current user can edit the note
*/
const canEdit = memberRole === MemberRole.Write;


return reply.send({
note: notePublic,
parentNote: parentNote,
accessRights: { canEdit: canEdit },
tools: noteTools,
});
});

Expand Down Expand Up @@ -222,7 +248,7 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
const { userId } = request;
const parentId = request.body.parentId;

const addedNote = await noteService.addNote(content as JSON, userId as number, parentId); // "authRequired" policy ensures that userId is not null
const addedNote = await noteService.addNote(content as Note['content'], userId as number, parentId); // "authRequired" policy ensures that userId is not null

/**
* Save note visit when note created
Expand Down Expand Up @@ -259,7 +285,7 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
notePublicId: NotePublicId,
},
Body: {
content: JSON;
content: Note['content'];
},
Reply: {
updatedAt: Note['updatedAt'],
Expand Down Expand Up @@ -289,7 +315,7 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
],
}, async (request, reply) => {
const noteId = request.note?.id as number;
const content = request.body.content as JSON;
const content = request.body.content;

const note = await noteService.updateNoteContentById(noteId, content);

Expand Down Expand Up @@ -426,6 +452,7 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
accessRights: {
canEdit: boolean,
},
tools: EditorTool[],
}| ErrorResponse,
}>('/resolve-hostname/:hostname', {
schema: {
Expand All @@ -444,6 +471,12 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
},
},
},
tools: {
type: 'array',
items: {
$ref: 'EditorToolSchema',
},
},
},

},
Expand Down Expand Up @@ -492,9 +525,21 @@ const NoteRouter: FastifyPluginCallback<NoteRouterOptions> = (fastify, opts, don
canEdit = memberRole === MemberRole.Write;
}

/**
* Get all tools used in the note
*/
const noteToolsNames = new Set<string>();

note.content.blocks.forEach((block: { type: string }) => {
noteToolsNames.add(block.type);
});

const noteTools = await editorToolsService.getToolsByNames(Array.from(noteToolsNames));

return reply.send({
note: notePublic,
accessRights: { canEdit: canEdit },
tools: noteTools,
});
});

Expand Down
11 changes: 11 additions & 0 deletions src/repository/editorTools.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ export default class EditorToolsRepository {
return tools;
}

/**
* Returns bunchh of tools by their names
*
* @param editorToolNames - unique tool names
*/
public async getToolsByNames(editorToolNames: EditorTool['name'][]): Promise<EditorTool[]> {
const tools = await this.storage.getToolsByNames(editorToolNames);

return tools;
}

/**
* Get all default tools
*/
Expand Down
15 changes: 15 additions & 0 deletions src/repository/storage/postgres/orm/sequelize/editorTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,19 @@ export default class UserSequelizeStorage {
public async getTools(): Promise<EditorTool[]> {
return await EditorToolModel.findAll();
}

/**
* Get bunch of tools by their names
*
* @param editorToolNames - tool names
*/
public async getToolsByNames(editorToolNames: EditorTool['name'][]): Promise<EditorTool[]> {
return await this.model.findAll({
where: {
name: {
[Op.in]: editorToolNames,
},
},
});
}
}
48 changes: 44 additions & 4 deletions src/tests/utils/database-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,29 @@ import type EditorTool from '@domain/entities/editorTools.ts';
import type NoteVisit from '@domain/entities/noteVisit.js';
import { nanoid } from 'nanoid';

/**
* Note content mock inserted if no content passed
*/
const DEFAULT_NOTE_CONTENT = {
blocks: [
{
id: 'mJDq8YbvqO',
type: 'paragraph',
data: {
text: 'text',
},
},
{
id: 'DeL0QehzGe',
type: 'header',
data: {
text: 'fdgsfdgfdsg',
level: 2,
},
},
],
};

/**
* default type for note mock creation attributes
*/
Expand Down Expand Up @@ -109,11 +132,11 @@ export default class DatabaseHelpers {
* If publicId is not passed, it's value in database would be created via `createPublicId()` method
*/
public async insertNote(note: NoteMockCreationAttributes): Promise<Note> {
const content = note.content ?? '{}';
const content = note.content ?? DEFAULT_NOTE_CONTENT;
const publicId = note.publicId ?? createPublicId();

const [results, _] = await this.orm.connection.query(`INSERT INTO public.notes ("content", "creator_id", "created_at", "updated_at", "public_id")
VALUES ('${content}', ${note.creatorId}, CURRENT_DATE, CURRENT_DATE, '${publicId}')
VALUES ('${JSON.stringify(content)}', ${note.creatorId}, CURRENT_DATE, CURRENT_DATE, '${publicId}')
RETURNING "id", "content", "creator_id" AS "creatorId", "public_id" AS "publicId", "created_at" AS "createdAt", "updated_at" AS "updatedAt"`,
{
type: QueryTypes.INSERT,
Expand Down Expand Up @@ -265,8 +288,8 @@ export default class DatabaseHelpers {
/**
* Truncates all tables and restarts all autoincrement sequences
*/
public async truncateTables(): Promise<unknown> {
return await this.orm.connection.query(`DO $$
public async truncateTables(): Promise<void> {
await this.orm.connection.query(`DO $$
DECLARE
-- table iterator
tbl RECORD;
Expand All @@ -292,5 +315,22 @@ export default class DatabaseHelpers {
EXECUTE format('ALTER sequence %s RESTART WITH 1', seq.sequence_name);
END LOOP;
END $$ LANGUAGE plpgsql;`);


/** Insert default tools */
await this.orm.connection.query(`
INSERT INTO public.editor_tools (name, title, export_name, source, is_default)
VALUES ('header', 'Heading', 'Header', '{"cdn": "https://cdn.jsdelivr.net/npm/@editorjs/[email protected]/dist/header.umd.min.js"}'::json, true);
`);

await this.orm.connection.query(`
INSERT INTO public.editor_tools (name, title, export_name, source, is_default)
VALUES ('paragraph', 'Paragraph', 'Paragraph', '{"cdn": "https://cdn.jsdelivr.net/npm/@editorjs/[email protected]/dist/paragraph.umd.min.js"}'::json, true);
`);

await this.orm.connection.query(`
INSERT INTO public.editor_tools (name, title, export_name, source, is_default)
VALUES ('list', 'List', 'List', '{"cdn": "https://cdn.jsdelivr.net/npm/@editorjs/[email protected]/dist/list.umd.min.js"}'::json, true);
`);
}
};

0 comments on commit 51ac441

Please sign in to comment.