Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
d1a0bd1
feat: add BaseNode and BaseNodeFolder models with migration
younocode Nov 25, 2025
516d008
feat: add tree component in ui-lib
younocode Nov 25, 2025
be2000a
feat: implement BaseNode and BaseNodeFolder functionality with CRUD o…
younocode Nov 25, 2025
2b44b71
feat: enhance migration script
younocode Nov 26, 2025
3852f6f
feat: add support for user last visit tracking and resource deletion …
younocode Nov 26, 2025
9bcb061
feat: implement permission management for BaseNode with role-based ac…
younocode Nov 26, 2025
8b75290
refactor: PinService to optimize resource fetching and enhance code …
younocode Nov 26, 2025
b803071
fix: router
younocode Nov 27, 2025
bd1c8b4
feat: base import/export/duplicae support base node
younocode Nov 27, 2025
c306493
test: add unit tests for BaseNodeService methods including SQL genera…
younocode Nov 27, 2025
46de704
feat: implement folder depth validation and enhance node movement log…
younocode Nov 27, 2025
c14dde6
feat: integrate performance caching for base node list
younocode Nov 27, 2025
c17c432
refactor: remove unused routes from BasePageRouter
younocode Nov 27, 2025
92a5ea0
feat: enhance dashboard renaming functionality with improved state ma…
younocode Nov 28, 2025
86d73df
refactor: simplify BaseNodeTree component by removing unnecessary sep…
younocode Nov 28, 2025
90cc4bc
feat: enhance QuickAction search
younocode Nov 28, 2025
d540bfb
fix: sorting for nodes in BaseImportService to ensure proper parent-c…
younocode Nov 28, 2025
0e86ded
fix: delete folder and pin list
younocode Nov 28, 2025
b3bf832
feat: add permanent delete functionality for base nodes and enhance d…
younocode Dec 1, 2025
6fdf1a3
feat: enhance error handling in BaseNodeService and BaseNodeFolderSer…
younocode Dec 1, 2025
bcfbeec
refactor: rename hooks and reorganize imports in base node feature
younocode Dec 1, 2025
35be331
refactor: remove console log and clean up imports in PinItem component
younocode Dec 1, 2025
822c85f
fix: pin sql
younocode Dec 1, 2025
2246966
fix: e2e
younocode Dec 1, 2025
53d8063
fix: sharedb presence handling
younocode Dec 1, 2025
6d06877
fix: e2e
younocode Dec 1, 2025
5936dee
refactor: optimize database transactions in BaseNodeService
younocode Dec 1, 2025
32ebf01
fix: improve URL generation in BaseNode components
younocode Dec 2, 2025
dbb13a6
refactor: remove unnecessary permission decorator and adjust layout i…
younocode Dec 2, 2025
608dcaf
feat: add validation for folder depth when moving nodes
younocode Dec 2, 2025
a5cfbd0
fix: refine anchorId logic in BaseNodeTree component for improved nod…
younocode Dec 2, 2025
ef387fc
fix: adjust emoji picker size in BaseNodeTree component for better UI…
younocode Dec 2, 2025
e9ade1f
fix: enhance expanded when create
younocode Dec 2, 2025
586577c
feat: implement auto-scroll functionality during drag in BaseNodeTree…
younocode Dec 2, 2025
ad5a43a
fix: update TreeItemLabel and TreeDragLine styles for improved visual…
younocode Dec 2, 2025
6ee36d3
fix: enhance canDrop logic in BaseNodeTree for improved item drop val…
younocode Dec 2, 2025
26afd8b
refactor: add resourceMeta in baseNodeSchema
younocode Dec 2, 2025
630e69a
fix: e2e
younocode Dec 2, 2025
b95d467
refactor: update folder creation and update endpoints to return struc…
younocode Dec 2, 2025
f3e4c38
fix: e2e
younocode Dec 2, 2025
4caf225
feat: add disallowDashboard setting and deprecation banner in dashboa…
younocode Dec 2, 2025
cbb6f3f
fix: type check
younocode Dec 3, 2025
f1c233a
feat: add loading state to BaseNodeContext and integrate skeleton loa…
younocode Dec 3, 2025
a697f9b
feat: enhance BaseNode service and UI to include defaultViewId in res…
younocode Dec 3, 2025
fb1441b
refactor: simplify URL construction in getNodeUrl and streamline tabl…
younocode Dec 3, 2025
19394ac
refactor: improve styling and structure in BaseNodeTree for better re…
younocode Dec 3, 2025
614551f
feat: add workflow state render
younocode Dec 3, 2025
ca1d658
fix: sync dataLoader returned undefined error
younocode Dec 3, 2025
6e6e78f
refactor: update styling in BaseNodeTree for improved layout and cons…
younocode Dec 3, 2025
17fb76c
refactor: remove setEditingNodeId when create and duplicate
younocode Dec 3, 2025
41d45ad
refactor: extract table creation logic for improved readability
younocode Dec 3, 2025
3095886
refactor: update dropdown menu width and enhance delete confirmation …
younocode Dec 4, 2025
05406cb
fix: common noun i18n
younocode Dec 4, 2025
230d4c6
feat: introduce useBaseNodeContext hook for improved context manageme…
younocode Dec 4, 2025
4da58b8
refactor: update useBaseNode for enhanced context management
younocode Dec 4, 2025
e0829b2
refactor: enhance BaseNodeTree component with edit mode support and i…
younocode Dec 4, 2025
879f067
feat: add onUpdateError callback to useBaseNodeCrud and BaseNodeTree …
younocode Dec 4, 2025
fdec091
refactor: improved UI consistency
younocode Dec 5, 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