Skip to content

Commit

Permalink
Merge pull request #82 from codex-team/feat/editor-tools-in-user
Browse files Browse the repository at this point in the history
Added user editor tools
  • Loading branch information
GeekaN2 authored Oct 22, 2023
2 parents 6add73b + e9c4cec commit 355ede3
Show file tree
Hide file tree
Showing 13 changed files with 281 additions and 46 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module.exports = {
'format': ['camelCase', 'PascalCase'],
'filter': {
// Allow "2xx" as a property name, used in the API response schema
'regex': '^(2xx)$',
'regex': '^(2xx|2\d{2}|application\/json)$',
'match': false,
},
},
Expand Down
16 changes: 13 additions & 3 deletions src/domain/entities/editorTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,30 @@
*/
export default interface EditorTool {
/**
* Unique identifier of the tool
* Unique identifier of the tool. Nano-ID
*/
id: string;

/**
* User-friendly plugin title
* Technical name of the tool, like 'header', 'list', 'linkTool'
*/
name: string;

/**
* User-friendly plugin title
*/
title: string;

/**
* Name of the tool class. Since it's imported globally,
* we need the class name to properly connect the tool to the editor
*/
class: string;
exportName: string;

/**
* Is plugin included by default in the editor
*/
isDefault?: boolean;

/**
* Source of the tool to get it's code
Expand Down
21 changes: 17 additions & 4 deletions src/domain/service/editorTools.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type EditorToolsRepository from '@repository/editorTools.repository.js';
import type EditorTool from '@domain/entities/editorTools.js';
import { createEditorToolId } from '@infrastructure/utils/id.js';

/**
* Editor tools service
Expand All @@ -26,13 +27,25 @@ export default class EditorToolsService {
return await this.repository.getTools();
}

/**
* Get bunch of editor tools by their ids
*
* @param editorToolIds - tool ids
*/
public async getToolsByIds(editorToolIds: EditorTool['id'][] ): Promise<EditorTool[] | null> {
return await this.repository.getToolsByIds(editorToolIds);
}

/**
* Adding custom editor tool
*
* @param tool - all data about the editor plugin
* @returns {Promise<EditorTool | null>} editor tool data
* @param editorTool - all data about the editor plugin
* @returns {Promise<EditorTool>} editor tool data
*/
public async addTool(tool: EditorTool): Promise<EditorTool | null> {
return await this.repository.addTools(tool);
public async addTool(editorTool: Omit<EditorTool, 'id'>): Promise<EditorTool> {
return await this.repository.addTool({
id: createEditorToolId(),
...editorTool,
});
}
}
29 changes: 25 additions & 4 deletions src/domain/service/user.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type UserRepository from '@repository/user.repository.js';
import { Provider } from '@repository/user.repository.js';
import type User from '@domain/entities/user.js';
import type { UserEditorTool } from '@domain/entities/userExtensions.js';
import type EditorTool from '@domain/entities/editorTools';

export {
Provider
Expand Down Expand Up @@ -47,13 +47,34 @@ export default class UserService {
}

/**
* Get installed user tools
* Get user extensions that contains only editoTools for now
* TODO: Simplify extenisons
*
* @param userId - user unique identifier
*/
public async getUserEditorTools(userId: User['id']): Promise<UserEditorTool[] | undefined> {
public async getUserExtensions(userId: User['id']): Promise<User['extensions']> {
const user = await this.getUserById(userId);

return user?.extensions?.editorTools;
return user?.extensions ?? {};
}

/**
* Adds editor tool to user settings by its id
*
* @param options - user id & editor tool
*/
public async addUserEditorTool({
userId,
editorToolId,
}: {
userId: User['id'],
editorToolId: EditorTool['id'],
}): Promise<void> {
return await this.repository.addUserEditorTool({
userId,
tool: {
id: editorToolId,
},
});
}
}
10 changes: 10 additions & 0 deletions src/infrastructure/utils/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,13 @@ import { nanoid } from 'nanoid';
export function createPublicId(length: number = 10): string {
return nanoid(length);
}

/**
* Create unique identifier for editor tools
* Used in editor tools and user settings
*
* @param length - id length
*/
export function createEditorToolId(length: number = 8): string {
return nanoid(length);
}
14 changes: 8 additions & 6 deletions src/presentation/http/http-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Policies from './policies/index.js';
import type { RequestParams, Response } from '@presentation/api.interface.js';
import NoteSettingsRouter from './router/noteSettings.js';
import NoteListRouter from '@presentation/http/router/noteList.js';
import { EditorToolSchema } from './schema/EditorTool.js';


const appServerLogger = getLogger('appServer');
Expand Down Expand Up @@ -132,8 +133,8 @@ export default class HttpApi implements Api {
url: 'http://localhost:1337',
description: 'Localhost environment',
}, {
url: 'https://notex.so',
description: 'Stage environment',
url: 'https://api.notex.so',
description: 'Production environment',
} ],
components: {
securitySchemes: {
Expand All @@ -142,12 +143,11 @@ export default class HttpApi implements Api {
description: 'Provied authorization uses OAuth 2 with Google',
flows: {
authorizationCode: {
authorizationUrl: 'https://notex.so/oauth/google/login',
authorizationUrl: 'https://api.notex.so/oauth/google/login',
scopes: {
// eslint-disable-next-line @typescript-eslint/naming-convention
'notes_management': 'Create, read, update and delete notes',
'notesManagement': 'Create, read, update and delete notes',
},
tokenUrl: '',
tokenUrl: 'https://api.notex.so/oauth/google/callback',
},
},
},
Expand Down Expand Up @@ -218,6 +218,7 @@ export default class HttpApi implements Api {
await this.server?.register(UserRouter, {
prefix: '/user',
userService: domainServices.userService,
editorToolsService: domainServices.editorToolsService,
});

await this.server?.register(AIRouter, {
Expand Down Expand Up @@ -266,6 +267,7 @@ export default class HttpApi implements Api {
private addSchema(): void {
this.server?.addSchema(UserSchema);
this.server?.addSchema(NoteSchema);
this.server?.addSchema(EditorToolSchema);
}

/**
Expand Down
46 changes: 44 additions & 2 deletions src/presentation/http/router/editorTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,27 @@ const EditorToolsRouter: FastifyPluginCallback<EditorToolsRouterOptions> = (fast
/**
* Get all avaiable editor tools
*/
fastify.get('/all', async (_, reply) => {
fastify.get('/all', {
schema: {
response: {
'2xx': {
description: 'Editor tool fields',
content: {
'application/json': {
schema: {
data: {
type: 'array',
items: {
$ref: 'EditorToolSchema',
},
},
},
},
},
},
},
},
}, async (_, reply) => {
const tools = await editorToolsService.getTools();

return reply.send({
Expand All @@ -40,7 +60,29 @@ const EditorToolsRouter: FastifyPluginCallback<EditorToolsRouterOptions> = (fast
/**
* Add editor tool to the library of all tools
*/
fastify.post<{ Body: EditorTool }>('/add-tool', async (request, reply) => {
fastify.post<{
Body: EditorTool
}>('/add-tool', {
schema: {
body: {
$ref: 'EditorToolSchema',
},
response: {
'2xx': {
description: 'Editor tool fields',
content: {
'application/json': {
schema: {
data: {
$ref: 'EditorToolSchema',
},
},
},
},
},
},
},
}, async (request, reply) => {
const editorTool = request.body;

const tool = await editorToolsService.addTool(editorTool);
Expand Down
65 changes: 64 additions & 1 deletion src/presentation/http/router/user.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { FastifyPluginCallback } from 'fastify';
import type UserService from '@domain/service/user.js';
import type User from '@domain/entities/user.js';
import type EditorToolsService from '@domain/service/editorTools';

/**
* Interface for the user router
Expand All @@ -10,6 +11,11 @@ interface UserRouterOptions {
* User service instance
*/
userService: UserService,

/**
* Service editor tool
*/
editorToolsService: EditorToolsService,
}

/**
Expand All @@ -24,6 +30,7 @@ const UserRouter: FastifyPluginCallback<UserRouterOptions> = (fastify, opts, don
* Manage user data
*/
const userService = opts.userService;
const editorToolsService = opts.editorToolsService;

/**
* Get user by session
Expand Down Expand Up @@ -64,16 +71,72 @@ const UserRouter: FastifyPluginCallback<UserRouterOptions> = (fastify, opts, don
'authRequired',
],
},
schema: {
response: {
'2xx': {
description: 'Editor tool fields',
content: {
'application/json': {
schema: {
data: {
type: 'array',
items: {
$ref: 'EditorToolSchema',
},
},
},
},
},
},
},
},
}, async (request, reply) => {
const userId = request.userId as number;

const editorTools = await userService.getUserEditorTools(userId) ?? [];
const userExtensions = await userService.getUserExtensions(userId);
const userEditorToolIds = userExtensions?.editorTools?.map(tools => tools.id) ?? [];
const editorTools = await editorToolsService.getToolsByIds(userEditorToolIds) ?? [];

return reply.send({
data: editorTools,
});
});

/**
* Add editor tool to user extensions.
* These editor tools are used when creating new notes.
* Tool is linked by it's id.
*/
fastify.post<{
Body: { toolId: string }
}>('/editor-tools', {
config: {
policy: [
'authRequired',
],
},
schema: {
body: {
toolId: {
type: 'string',
description: 'Unique editor tool id',
},
},
},
}, async (request, reply) => {
const editorToolId = request.body.toolId;
const userId = request.userId as number;

await userService.addUserEditorTool({
userId,
editorToolId,
});

return reply.send({
data: editorToolId,
});
});

done();
};

Expand Down
38 changes: 38 additions & 0 deletions src/presentation/http/schema/EditorTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export const EditorToolSchema = {
$id: 'EditorToolSchema',
type: 'object',
properties: {
id: {
type: 'string',
readOnly: true,
description: 'Unique tool id',
},
name: {
type: 'string',
description: 'Plugin id that editor will use, e.g. "warning", "list", "linkTool"',
},
title: {
type: 'string',
description: 'User-friendly name that will be shown in marketplace, .e.g "Warning tool 3000"',
},
exportName: {
type: 'string',
description: 'Name of the plugin\'s class, e.g. "LinkTool", "Checklist", "Header"',
},
isDefault: {
type: 'boolean',
description: 'Is plugin included by default in the editor',
default: false,
},
source: {
type: 'object',
properties: {
cdn: {
type: 'string',
description: 'Tool URL in content delivery network',
},
},
},
},
};

Loading

0 comments on commit 355ede3

Please sign in to comment.