diff --git a/src/application/i18n/messages/en.json b/src/application/i18n/messages/en.json index 38546f2c..9124dc13 100644 --- a/src/application/i18n/messages/en.json +++ b/src/application/i18n/messages/en.json @@ -91,6 +91,14 @@ "500": "Unknown error happened", "default": "Something went wrong" }, + "join": { + "title": "Joining a note team...", + "messages": { + "linkExpired": "This join link has been expired", + "validationError": "Wrong invitation hash specified", + "unauthorized": "You must be authenticated to access this resource" + } + }, "marketplace": { "title": "Marketplace", "listOfTools": "Tools", @@ -146,6 +154,7 @@ "noteSettings": "Note settings", "marketplace": "Marketplace", "addTool": "Add tool", - "notFound": "Not found" + "notFound": "Not found", + "joinTeam": "Join" } } diff --git a/src/application/router/routes.ts b/src/application/router/routes.ts index 69fe19ad..aea05f2d 100644 --- a/src/application/router/routes.ts +++ b/src/application/router/routes.ts @@ -4,6 +4,7 @@ import Landing from '@/presentation/pages/Landing.vue'; import Settings from '@/presentation/pages/Settings.vue'; import NoteSettings from '@/presentation/pages/NoteSettings.vue'; import ErrorPage from '@/presentation/pages/Error.vue'; +import JoinPage from '@/presentation/pages/Join.vue'; import type { RouteRecordRaw } from 'vue-router'; import AddTool from '@/presentation/pages/marketplace/AddTool.vue'; import MarketplacePage from '@/presentation/pages/marketplace/MarketplacePage.vue'; @@ -114,6 +115,30 @@ const routes: RouteRecordRaw[] = [ }, }], }, + { + name: 'join', + path: '/join/:hash', + component: JoinPage, + props: route => ({ + invitationHash: String(route.params.hash), + }), + meta: { + pageTitleI18n: 'pages.joinTeam', + discardTabOnLeave: true, + }, + }, + { + name: 'join', + path: '/join/:hash', + component: JoinPage, + props: route => ({ + invitationHash: String(route.params.hash), + }), + meta: { + pageTitleI18n: 'pages.joinTeam', + discardTabOnLeave: true, + }, + }, /** * 404 page diff --git a/src/application/services/useTeam.ts b/src/application/services/useTeam.ts new file mode 100644 index 00000000..6334dfc6 --- /dev/null +++ b/src/application/services/useTeam.ts @@ -0,0 +1,27 @@ +import type { InvitationHash } from '@/domain/entities/NoteSettings'; +import type { TeamMember } from '@/domain/entities/Team'; +import { teamService } from '@/domain/index'; + +interface useTeamComposableState { + /** + * Function for adding user to note's team by invitation hash + * @param hash - invitation hash of the certain note + * @returns Team member or null + */ + joinNoteTeamByHash: (hash: InvitationHash) => Promise; +} + +export default function useTeam(): useTeamComposableState { + /** + * Function for adding user to note's team by invitation hash + * @param hash - invitation hash of the certain note + * @returns Team member or null + */ + async function joinNoteTeamByHash(hash: InvitationHash): Promise { + return await teamService.joinNoteTeamByHash(hash); + } + + return { + joinNoteTeamByHash, + }; +} diff --git a/src/domain/entities/Team.ts b/src/domain/entities/Team.ts index 911472c7..e369e211 100644 --- a/src/domain/entities/Team.ts +++ b/src/domain/entities/Team.ts @@ -1,3 +1,4 @@ +import type { NoteId } from './Note.js'; import type { User } from './User.js'; export enum MemberRole { @@ -22,6 +23,11 @@ export interface TeamMember { */ id: number; + /** + * User is in team of the note with such an id + */ + noteId: NoteId; + /** * Team member user */ diff --git a/src/domain/index.ts b/src/domain/index.ts index 78e56bc5..4a191ef8 100644 --- a/src/domain/index.ts +++ b/src/domain/index.ts @@ -8,6 +8,7 @@ import EventBus from './event-bus'; import NoteListService from './noteList.service'; import EditorToolsService from '@/domain/editorTools.service'; import WorkspaceService from './workspace.service'; +import TeamService from './team.service'; /** * Get API url from environment */ @@ -34,6 +35,7 @@ const noteSettingsService = new NoteSettingsService(repositories.noteSettings, r const authService = new AuthService(eventBus, repositories.auth); const userService = new UserService(eventBus, repositories.user); const marketplaceService = new MarketplaceService(repositories.marketplace); +const teamService = new TeamService(repositories.team); /** * App State — is a read-only combination of app Stores. @@ -57,5 +59,6 @@ export { authService, userService, marketplaceService, - workspaceService + workspaceService, + teamService }; diff --git a/src/domain/team.repository.interface.ts b/src/domain/team.repository.interface.ts new file mode 100644 index 00000000..772cfe59 --- /dev/null +++ b/src/domain/team.repository.interface.ts @@ -0,0 +1,15 @@ +import type { InvitationHash } from '@/domain/entities/NoteSettings'; +import type { TeamMember } from '@/domain/entities/Team'; + +/** + * Repository interface describes the methods that required by domain for its business logic implementation + */ +export default interface TeamRepositoryInterface { + /** + * Function for adding user to note's team by invitation hash + * @param hash - invitation hash of the certain note + * @returns Team member or null + */ + joinNoteTeamByHash: (hash: InvitationHash) => Promise; + +} diff --git a/src/domain/team.service.ts b/src/domain/team.service.ts new file mode 100644 index 00000000..429aec0f --- /dev/null +++ b/src/domain/team.service.ts @@ -0,0 +1,26 @@ +import type TeamRepository from '@/domain/team.repository.interface'; +import type { InvitationHash } from './entities/NoteSettings'; +import type { TeamMember } from './entities/Team'; + +/** + * Team service + */ +export default class TeamService { + /** + * Team repository + */ + private readonly teamRepository: TeamRepository; + + constructor(teamRepository: TeamRepository) { + this.teamRepository = teamRepository; + } + + /** + * Function for adding user to note's team by invitation hash + * @param hash - invitation hash of the certain note + * @returns Team member or null + */ + public async joinNoteTeamByHash(hash: InvitationHash): Promise { + return this.teamRepository.joinNoteTeamByHash(hash); + } +} diff --git a/src/infrastructure/index.ts b/src/infrastructure/index.ts index cb28d335..95cf0122 100644 --- a/src/infrastructure/index.ts +++ b/src/infrastructure/index.ts @@ -16,6 +16,7 @@ import { EditorToolsStore } from '@/infrastructure/storage/editorTools.ts'; import EditorToolsRepository from '@/infrastructure/editorTools.repository'; import EditorToolsTransport from '@/infrastructure/transport/editorTools.transport'; import NoteAttachmentUploaderRepository from './noteAttachmentUploader.repository'; +import TeamRepository from '@/infrastructure/team.repository'; /** * Repositories @@ -60,6 +61,11 @@ export interface Repositories { * Working with all pages user is currently using */ workspace: WorkspaceRepository; + + /** + * Working with teams + */ + team: TeamRepository; } /** @@ -126,6 +132,7 @@ export function init(noteApiUrl: string, eventBus: EventBus): Repositories { const editorToolsRepository = new EditorToolsRepository(editorToolsStore, editorToolsTransport); const noteAttachmentUploaderRepository = new NoteAttachmentUploaderRepository(notesApiTransport); const workspaceRepository = new WorkspaceRepository(openedPagesStore); + const teamRepository = new TeamRepository(notesApiTransport); return { note: noteRepository, @@ -136,5 +143,6 @@ export function init(noteApiUrl: string, eventBus: EventBus): Repositories { editorTools: editorToolsRepository, noteAttachmentUploader: noteAttachmentUploaderRepository, workspace: workspaceRepository, + team: teamRepository, }; } diff --git a/src/infrastructure/team.repository.ts b/src/infrastructure/team.repository.ts new file mode 100644 index 00000000..e29597b8 --- /dev/null +++ b/src/infrastructure/team.repository.ts @@ -0,0 +1,34 @@ +import type TeamRepositoryInterface from '@/domain/team.repository.interface'; +import type NotesApiTransport from '@/infrastructure/transport/notes-api'; +import type { InvitationHash } from '@/domain/entities/NoteSettings'; +import type { TeamMember } from '@/domain/entities/Team'; + +/** + * Team repository + */ +export default class TeamRepository implements TeamRepositoryInterface { + /** + * Transport instance + */ + private readonly transport: NotesApiTransport; + /** + * Note repository constructor + * @param notesApiTransport - notes api transport instance + */ + constructor(notesApiTransport: NotesApiTransport) { + this.transport = notesApiTransport; + } + + /** + * Function for adding user to note's team by invitation hash + * @param hash - invitation hash of the certain note + * @returns Team member or null + */ + public async joinNoteTeamByHash(hash: InvitationHash): Promise { + let res: TeamMember | null = null; + + res = await this.transport.post({ endpoint: '/join/' + hash }); + + return res; + } +} diff --git a/src/presentation/pages/Join.vue b/src/presentation/pages/Join.vue new file mode 100644 index 00000000..502eae1c --- /dev/null +++ b/src/presentation/pages/Join.vue @@ -0,0 +1,91 @@ + + + + +