Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ad86ddf
feat: add BaseNode and BaseNodeFolder models with migration
younocode Nov 25, 2025
8fcca5f
feat: add tree component in ui-lib
younocode Nov 25, 2025
e06e86a
feat: implement BaseNode and BaseNodeFolder functionality with CRUD o…
younocode Nov 25, 2025
9a56299
feat: enhance migration script
younocode Nov 26, 2025
2bedc89
feat: add support for user last visit tracking and resource deletion …
younocode Nov 26, 2025
669f022
feat: implement permission management for BaseNode with role-based ac…
younocode Nov 26, 2025
98d651c
refactor: PinService to optimize resource fetching and enhance code …
younocode Nov 26, 2025
e6aac91
fix: router
younocode Nov 27, 2025
0ce10c1
feat: base import/export/duplicae support base node
younocode Nov 27, 2025
8b298a4
test: add unit tests for BaseNodeService methods including SQL genera…
younocode Nov 27, 2025
d39f3a7
feat: implement folder depth validation and enhance node movement log…
younocode Nov 27, 2025
aec87dc
feat: integrate performance caching for base node list
younocode Nov 27, 2025
83f1f7d
refactor: remove unused routes from BasePageRouter
younocode Nov 27, 2025
e0d3789
feat: enhance dashboard renaming functionality with improved state ma…
younocode Nov 28, 2025
9591598
refactor: simplify BaseNodeTree component by removing unnecessary sep…
younocode Nov 28, 2025
756354b
feat: enhance QuickAction search
younocode Nov 28, 2025
9248011
fix: sorting for nodes in BaseImportService to ensure proper parent-c…
younocode Nov 28, 2025
ea2e040
fix: delete folder and pin list
younocode Nov 28, 2025
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
2 changes: 2 additions & 0 deletions apps/nestjs-backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { AiModule } from './features/ai/ai.module';
import { AttachmentsModule } from './features/attachments/attachments.module';
import { AuthModule } from './features/auth/auth.module';
import { BaseModule } from './features/base/base.module';
import { BaseNodeModule } from './features/base-node/base-node.module';
import { ChatModule } from './features/chat/chat.module';
import { CollaboratorModule } from './features/collaborator/collaborator.module';
import { CommentOpenApiModule } from './features/comment/comment-open-api.module';
Expand Down Expand Up @@ -59,6 +60,7 @@ export const appModules = {
FieldOpenApiModule,
TemplateOpenApiModule,
BaseModule,
BaseNodeModule,
IntegrityModule,
ChatModule,
AttachmentsModule,
Expand Down
59 changes: 59 additions & 0 deletions apps/nestjs-backend/src/event-emitter/events/app/app.event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { match } from 'ts-pattern';
import type { IEventContext } from '../core-event';
import { CoreEvent } from '../core-event';
import { Events } from '../event.enum';

interface IAppVo {
id: string;
name: string;
}

type IAppCreatePayload = { baseId: string; app: IAppVo };
type IAppDeletePayload = { baseId: string; appId: string };
type IAppUpdatePayload = { baseId: string; app: IAppVo };

export class AppCreateEvent extends CoreEvent<IAppCreatePayload> {
public readonly name = Events.APP_CREATE;

constructor(baseId: string, app: IAppVo, context: IEventContext) {
super({ baseId, app }, context);
}
}

export class AppDeleteEvent extends CoreEvent<IAppDeletePayload> {
public readonly name = Events.APP_DELETE;
constructor(baseId: string, appId: string, context: IEventContext) {
super({ baseId, appId }, context);
}
}

export class AppUpdateEvent extends CoreEvent<IAppUpdatePayload> {
public readonly name = Events.APP_UPDATE;

constructor(baseId: string, app: IAppVo, context: IEventContext) {
super({ baseId, app }, context);
}
}

export class AppEventFactory {
static create(
name: string,
payload: IAppCreatePayload | IAppDeletePayload | IAppUpdatePayload,
context: IEventContext
) {
return match(name)
.with(Events.APP_CREATE, () => {
const { baseId, app } = payload as IAppCreatePayload;
return new AppCreateEvent(baseId, app, context);
})
.with(Events.APP_UPDATE, () => {
const { baseId, app } = payload as IAppUpdatePayload;
return new AppUpdateEvent(baseId, app, context);
})
.with(Events.APP_DELETE, () => {
const { baseId, appId } = payload as IAppDeletePayload;
return new AppDeleteEvent(baseId, appId, context);
})
.otherwise(() => null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { match } from 'ts-pattern';
import type { IEventContext } from '../../core-event';
import { CoreEvent } from '../../core-event';
import { Events } from '../../event.enum';

type IBaseFolder = {
id: string;
name: string;
};

type IBaseFolderCreatePayload = { baseId: string; folder: IBaseFolder };
type IBaseFolderDeletePayload = { baseId: string; folderId: string };
type IBaseFolderUpdatePayload = IBaseFolderCreatePayload;

export class BaseFolderCreateEvent extends CoreEvent<IBaseFolderCreatePayload> {
public readonly name = Events.BASE_FOLDER_CREATE;

constructor(payload: IBaseFolderCreatePayload, context: IEventContext) {
super(payload, context);
}
}

export class BaseFolderDeleteEvent extends CoreEvent<IBaseFolderDeletePayload> {
public readonly name = Events.BASE_FOLDER_DELETE;
constructor(payload: IBaseFolderDeletePayload, context: IEventContext) {
super(payload, context);
}
}

export class BaseFolderUpdateEvent extends CoreEvent<IBaseFolderUpdatePayload> {
public readonly name = Events.BASE_FOLDER_UPDATE;

constructor(payload: IBaseFolderUpdatePayload, context: IEventContext) {
super(payload, context);
}
}

export class BaseFolderEventFactory {
static create(
name: string,
payload: IBaseFolderCreatePayload | IBaseFolderDeletePayload | IBaseFolderUpdatePayload,
context: IEventContext
) {
return match(name)
.with(Events.BASE_FOLDER_CREATE, () => {
const { baseId, folder } = payload as IBaseFolderCreatePayload;
return new BaseFolderCreateEvent({ baseId, folder }, context);
})
.with(Events.BASE_FOLDER_DELETE, () => {
const { baseId, folderId } = payload as IBaseFolderDeletePayload;
return new BaseFolderDeleteEvent({ baseId, folderId }, context);
})
.with(Events.BASE_FOLDER_UPDATE, () => {
const { baseId, folder } = payload as IBaseFolderUpdatePayload;
return new BaseFolderUpdateEvent({ baseId, folder }, context);
})
.otherwise(() => null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { ICreateDashboardVo } from '@teable/openapi';
import { match } from 'ts-pattern';
import type { IEventContext } from '../core-event';
import { CoreEvent } from '../core-event';
import { Events } from '../event.enum';

type IDashboardCreatePayload = { baseId: string; dashboard: ICreateDashboardVo };
type IDashboardUpdatePayload = { baseId: string; dashboard: ICreateDashboardVo };
type IDashboardDeletePayload = { baseId: string; dashboardId: string };

export class DashboardCreateEvent extends CoreEvent<IDashboardCreatePayload> {
public readonly name = Events.DASHBOARD_CREATE;

constructor(payload: IDashboardCreatePayload, context: IEventContext) {
super(payload, context);
}
}

export class DashboardDeleteEvent extends CoreEvent<IDashboardDeletePayload> {
public readonly name = Events.DASHBOARD_DELETE;
constructor(payload: IDashboardDeletePayload, context: IEventContext) {
super(payload, context);
}
}

export class DashboardUpdateEvent extends CoreEvent<IDashboardUpdatePayload> {
public readonly name = Events.DASHBOARD_UPDATE;

constructor(payload: IDashboardUpdatePayload, context: IEventContext) {
super(payload, context);
}
}

export class DashboardEventFactory {
static create(
name: string,
payload: IDashboardCreatePayload | IDashboardDeletePayload | IDashboardUpdatePayload,
context: IEventContext
) {
return match(name)
.with(Events.DASHBOARD_CREATE, () => {
const { baseId, dashboard } = payload as IDashboardCreatePayload;
return new DashboardCreateEvent({ baseId, dashboard }, context);
})
.with(Events.DASHBOARD_DELETE, () => {
const { baseId, dashboardId } = payload as IDashboardDeletePayload;
return new DashboardDeleteEvent({ baseId, dashboardId }, context);
})
.with(Events.DASHBOARD_UPDATE, () => {
const { baseId, dashboard } = payload as IDashboardUpdatePayload;
return new DashboardUpdateEvent({ baseId, dashboard }, context);
})
.otherwise(() => null);
}
}
15 changes: 15 additions & 0 deletions apps/nestjs-backend/src/event-emitter/events/event.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,24 @@ export enum Events {
COLLABORATOR_CREATE = 'collaborator.create',
COLLABORATOR_DELETE = 'collaborator.delete',

BASE_FOLDER_CREATE = 'base.folder.create',
BASE_FOLDER_DELETE = 'base.folder.delete',
BASE_FOLDER_UPDATE = 'base.folder.update',

DASHBOARD_CREATE = 'dashboard.create',
DASHBOARD_DELETE = 'dashboard.delete',
DASHBOARD_UPDATE = 'dashboard.update',

WORKFLOW_CREATE = 'workflow.create',
WORKFLOW_DELETE = 'workflow.delete',
WORKFLOW_UPDATE = 'workflow.update',
WORKFLOW_ACTIVATE = 'workflow.activate',
WORKFLOW_DEACTIVATE = 'workflow.deactivate',

APP_CREATE = 'app.create',
APP_DELETE = 'app.delete',
APP_UPDATE = 'app.update',

CROP_IMAGE = 'crop.image',
CROP_IMAGE_COMPLETE = 'crop.image.complete',

Expand Down
4 changes: 4 additions & 0 deletions apps/nestjs-backend/src/event-emitter/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ export * from './event.enum';
export * from './core-event';
export * from './op-event';
export * from './base/base.event';
export * from './base/folder/base.folder.event';
export * from './space/space.event';
export * from './space/collaborator.event';
export * from './table';
export * from './dashboard/dashboard.event';
export * from './workflow/workflow.event';
export * from './app/app.event';
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { match } from 'ts-pattern';
import type { IEventContext } from '../core-event';
import { CoreEvent } from '../core-event';
import { Events } from '../event.enum';

interface IWorkflowVo {
id: string;
name: string;
}

type IWorkflowCreatePayload = { baseId: string; workflow: IWorkflowVo };
type IWorkflowDeletePayload = { baseId: string; workflowId: string };
type IWorkflowUpdatePayload = IWorkflowCreatePayload;

export class WorkflowCreateEvent extends CoreEvent<IWorkflowCreatePayload> {
public readonly name = Events.WORKFLOW_CREATE;

constructor(payload: IWorkflowCreatePayload, context: IEventContext) {
super(payload, context);
}
}

export class WorkflowDeleteEvent extends CoreEvent<IWorkflowDeletePayload> {
public readonly name = Events.WORKFLOW_DELETE;
constructor(payload: IWorkflowDeletePayload, context: IEventContext) {
super(payload, context);
}
}

export class WorkflowUpdateEvent extends CoreEvent<IWorkflowUpdatePayload> {
public readonly name = Events.WORKFLOW_UPDATE;

constructor(payload: IWorkflowUpdatePayload, context: IEventContext) {
super(payload, context);
}
}

export class WorkflowEventFactory {
static create(
name: string,
payload: IWorkflowCreatePayload | IWorkflowDeletePayload | IWorkflowUpdatePayload,
context: IEventContext
) {
return match(name)
.with(Events.WORKFLOW_CREATE, () => {
const { baseId, workflow } = payload as IWorkflowCreatePayload;
return new WorkflowCreateEvent({ baseId, workflow }, context);
})
.with(Events.WORKFLOW_DELETE, () => {
const { baseId, workflowId } = payload as IWorkflowDeletePayload;
return new WorkflowDeleteEvent({ baseId, workflowId }, context);
})
.with(Events.WORKFLOW_UPDATE, () => {
const { baseId, workflow } = payload as IWorkflowUpdatePayload;
return new WorkflowUpdateEvent({ baseId, workflow }, context);
})
.otherwise(() => null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ import { match, P } from 'ts-pattern';
import { EMIT_EVENT_NAME } from '../decorators/emit-controller-event.decorator';
import { EventEmitterService } from '../event-emitter.service';
import type { IEventContext } from '../events';
import { Events, BaseEventFactory, SpaceEventFactory } from '../events';
import {
Events,
BaseEventFactory,
SpaceEventFactory,
DashboardEventFactory,
AppEventFactory,
WorkflowEventFactory,
} from '../events';

@Injectable()
export class EventMiddleware implements NestInterceptor {
Expand Down Expand Up @@ -69,6 +76,36 @@ export class EventMiddleware implements NestInterceptor {
.with(P.union(Events.SPACE_CREATE, Events.SPACE_UPDATE), () =>
SpaceEventFactory.create(eventName, { space: resolveData, ...reqParams }, eventContext)
)
.with(Events.WORKFLOW_DELETE, () =>
WorkflowEventFactory.create(eventName, { ...resolveData, ...reqParams }, eventContext)
)
.with(P.union(Events.WORKFLOW_CREATE, Events.WORKFLOW_UPDATE), () =>
WorkflowEventFactory.create(
eventName,
{ baseId: reqParams.baseId, workflow: resolveData, ...reqParams },
eventContext
)
)
.with(Events.APP_DELETE, () =>
AppEventFactory.create(eventName, { ...resolveData, ...reqParams }, eventContext)
)
.with(P.union(Events.APP_CREATE, Events.APP_UPDATE), () =>
AppEventFactory.create(
eventName,
{ baseId: reqParams.baseId, app: resolveData, ...reqParams },
eventContext
)
)
.with(Events.DASHBOARD_DELETE, () =>
DashboardEventFactory.create(eventName, { ...resolveData, ...reqParams }, eventContext)
)
.with(P.union(Events.DASHBOARD_CREATE, Events.DASHBOARD_UPDATE), () =>
DashboardEventFactory.create(
eventName,
{ baseId: reqParams.baseId, dashboard: resolveData, ...reqParams },
eventContext
)
)
.otherwise(() => null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SetMetadata } from '@nestjs/common';
import type { BaseNodeAction } from '@teable/core';

export const BASE_NODE_PERMISSIONS_KEY = 'baseNodePermissions';

// eslint-disable-next-line @typescript-eslint/naming-convention
export const BaseNodePermissions = (...permissions: BaseNodeAction[]) =>
SetMetadata(BASE_NODE_PERMISSIONS_KEY, permissions);
Loading
Loading