Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(editor-tools): default tools added to user tools, global "underscore" options in models #94

Merged
merged 8 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions migrations/tenant/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

-- Adds "editor_tools" column at "users" if not exists
DO $$
BEGIN
IF NOT EXISTS(SELECT *
FROM information_schema.columns
WHERE table_name='users' and column_name='editor_tools')
THEN
ALTER TABLE "public"."users" ADD COLUMN "editor_tools" jsonb;
END IF;
END $$;

-- Removes "extensions" column at "users" if exists
DO $$
BEGIN
IF EXISTS(SELECT *
FROM information_schema.columns
WHERE table_name='users' and column_name='extensions')
THEN
ALTER TABLE "public"."users" DROP COLUMN "extensions";
END IF;
END $$;
33 changes: 33 additions & 0 deletions migrations/tenant/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-- "editor_tools" table:

-- Rename column "isDefault" to "is_default" if "isDefault" exists and "is_default" not exists
DO $$
BEGIN
IF EXISTS(SELECT *
FROM information_schema.columns
WHERE table_name='editor_tools' and column_name='isDefault')
THEN
IF NOT EXISTS(SELECT *
FROM information_schema.columns
WHERE table_name='editor_tools' and column_name='is_default')
THEN
ALTER TABLE "public"."editor_tools" RENAME COLUMN "isDefault" TO "is_default";
END IF;
END IF;
END $$;

-- Rename column "exportName" to "export_name" if "exportName" exists and "export_name" not exists
DO $$
BEGIN
IF EXISTS(SELECT *
FROM information_schema.columns
WHERE table_name='editor_tools' and column_name='exportName')
THEN
IF NOT EXISTS(SELECT *
FROM information_schema.columns
WHERE table_name='editor_tools' and column_name='export_name')
THEN
ALTER TABLE "public"."editor_tools" RENAME COLUMN "exportName" TO "export_name";
END IF;
END IF;
END $$;
7 changes: 3 additions & 4 deletions src/domain/entities/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type UserExtensions from '@domain/entities/userExtensions.js';
import type EditorTool from './editorTools';

/**
* User entity
Expand Down Expand Up @@ -30,8 +30,7 @@ export default interface User {
photo?: string;

/**
* Custom plugins from the marketplace that improve
* editor or notes environment
* Custom plugins ids from the marketplace that improve editor or notes environment
*/
extensions?: UserExtensions;
editorTools?: EditorTool['id'][];
}
19 changes: 0 additions & 19 deletions src/domain/entities/userExtensions.ts

This file was deleted.

10 changes: 7 additions & 3 deletions src/domain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,14 @@ export function init(repositories: Repositories, appConfig: AppConfig): DomainSe
repositories.userSessionRepository
);

const userService = new UserService(repositories.userRepository);

const aiService = new AIService(repositories.aiRepository);
const editorToolsService = new EditorToolsService(repositories.editorToolsRepository);
const userService = new UserService(repositories.userRepository, {
editorTools: editorToolsService,
/**
* @todo find a way how to resolve circular dependency
*/
});
const aiService = new AIService(repositories.aiRepository);

return {
noteService,
Expand Down
12 changes: 10 additions & 2 deletions src/domain/service/editorTools.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type EditorToolsRepository from '@repository/editorTools.repository.js';
import type EditorTool from '@domain/entities/editorTools.js';
import { createEditorToolId } from '@infrastructure/utils/id.js';
import type EditorToolsServiceSharedMethods from './shared/editorTools.js';

/**
* Editor tools service
*/
export default class EditorToolsService {
export default class EditorToolsService implements EditorToolsServiceSharedMethods {
/**
* User repository instance
*/
Expand All @@ -32,10 +33,17 @@ export default class EditorToolsService {
*
* @param editorToolIds - tool ids
*/
public async getToolsByIds(editorToolIds: EditorTool['id'][] ): Promise<EditorTool[] | null> {
public async getToolsByIds(editorToolIds: EditorTool['id'][]): Promise<EditorTool[]> {
return await this.repository.getToolsByIds(editorToolIds);
}

/**
* Return tools that are available at Editor by default
*/
public async getDefaultTools(): Promise<EditorTool[]> {
return await this.repository.getDefaultTools();
}

/**
* Adding custom editor tool
*
Expand Down
30 changes: 30 additions & 0 deletions src/domain/service/shared/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Shared Domain Services

Sometimes you may want to call some domain method from other domain. We can inject them in constructor, but it creates a direct dependency.

One way do decouple domains is to create a Shared Interfaces — domain will "expose" some public methods though it. You can call it "Contract".
So dependant domain will depend on it instead of direct dependency.


## Example

```ts
interface DomainASharedMethods {
someMethodA: () => void;
}

export type SharedDomainMethods = {
domainA: DomainASharedMethods;
};


class DomainA implements DomainASharedMethods {
public someMethodA (){}
}

class DomainB {
constructor(private readonly shared: SharedDomainMethods) {
this.shared.domainA.someMethodA(); // here we call method of Domain A, but without direct dependency
}
}
```
19 changes: 19 additions & 0 deletions src/domain/service/shared/editorTools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type EditorTool from '@domain/entities/editorTools';

/**
* Which methods of Domain can be used by other domains
* Uses to decouple domains from each other
*/
export default interface EditorToolsServiceSharedMethods {
/**
* Return tools that are available at Editor by default
*/
getDefaultTools(): Promise<EditorTool[]>;

/**
* Get bunch of editor tools by their ids
*
* @param ids - tool ids to resolve
*/
getToolsByIds(ids: EditorTool['id'][]): Promise<EditorTool[]>;
}
5 changes: 5 additions & 0 deletions src/domain/service/shared/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type EditorToolsServiceSharedMethods from './editorTools';

export type SharedDomainMethods = {
editorTools: EditorToolsServiceSharedMethods;
};
33 changes: 23 additions & 10 deletions src/domain/service/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,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 EditorTool from '@domain/entities/editorTools';
import type { SharedDomainMethods } from './shared/index.js';

export {
Provider
Expand All @@ -20,8 +21,9 @@ export default class UserService {
* User service constructor
*
* @param repository - user repository instance
* @param shared - shared domains
*/
constructor(repository: UserRepository) {
constructor(repository: UserRepository, private readonly shared: SharedDomainMethods) {
this.repository = repository;
}

Expand All @@ -47,15 +49,28 @@ export default class UserService {
}

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

return user?.extensions ?? {};
if (user === null) {
throw new Error('User not found');
}

const userToolsIds = user.editorTools ?? [];
const defaultTools = await this.shared.editorTools.getDefaultTools();
const uniqueDefaultEditorTools = defaultTools.filter(({ id }) => !userToolsIds.includes(id));
const userTools = await this.shared.editorTools.getToolsByIds(userToolsIds) ?? [];

/**
* Combine user tools and default tools
*
* @todo load tools in notes service
*/
return [...userTools, ...uniqueDefaultEditorTools];
}

/**
Expand All @@ -65,16 +80,14 @@ export default class UserService {
*/
public async addUserEditorTool({
userId,
editorToolId,
toolId,
}: {
userId: User['id'],
editorToolId: EditorTool['id'],
toolId: EditorTool['id'],
}): Promise<void> {
return await this.repository.addUserEditorTool({
userId,
tool: {
id: editorToolId,
},
toolId,
});
}
}
15 changes: 6 additions & 9 deletions src/presentation/http/router/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ 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 @@ -63,7 +62,7 @@ const UserRouter: FastifyPluginCallback<UserRouterOptions> = (fastify, opts, don
});

/**
* Get user extensions
* Get user editor tools
*/
fastify.get('/editor-tools', {
config: {
Expand Down Expand Up @@ -93,12 +92,10 @@ const UserRouter: FastifyPluginCallback<UserRouterOptions> = (fastify, opts, don
}, async (request, reply) => {
const userId = request.userId as number;

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

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

Expand All @@ -124,16 +121,16 @@ const UserRouter: FastifyPluginCallback<UserRouterOptions> = (fastify, opts, don
},
},
}, async (request, reply) => {
const editorToolId = request.body.toolId;
const toolId = request.body.toolId;
const userId = request.userId as number;

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

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

Expand Down
7 changes: 7 additions & 0 deletions src/presentation/http/schema/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,11 @@ export const UserSchema = {
name: { type: 'string' },
photo: { type: 'string' },
},
editorTools: {
type: 'array',
description: 'List of editor tools ids installed by user from Marketplace',
items: {
type: 'string',
},
},
};
7 changes: 7 additions & 0 deletions src/repository/editorTools.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ export default class EditorToolsRepository {
return tools;
}

/**
* Get all default tools
*/
public async getDefaultTools(): Promise<EditorTool[]> {
return await this.storage.getDefaultTools();
}

/**
* Get all editor tools
*/
Expand Down
2 changes: 0 additions & 2 deletions src/repository/note.repository.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Note, NoteCreationAttributes, NoteInternalId, NotePublicId } from '@domain/entities/note.js';
import type NoteStorage from '@repository/storage/note.storage.js';
import type { NoteList } from '@domain/entities/noteList.js';

/**
* Repository allows accessing data from business-logic (domain) level
Expand Down Expand Up @@ -85,7 +84,6 @@ export default class NoteRepository {
* @param id - note creator id
* @param offset - number of skipped notes
* @param limit - number of notes to get
* @returns { Promise<NoteList> } note
*/
public async getNoteListByCreatorId(id: number, offset: number, limit: number): Promise<Note[]> {
return await this.storage.getNoteListByCreatorId(id, offset, limit);
Expand Down
Loading