From f26eb0fa490c25669f82a4e04185e9f21007b125 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Wed, 9 Oct 2024 16:07:37 +0800 Subject: [PATCH 1/5] feat: create from shared base --- .../(share)/create-from-share/[shareId]/+page.svelte | 4 ++-- .../src/routes/(share)/s/b/[shareId]/+layout.svelte | 10 +++------- .../src/handlers/create-from-share.command-handler.ts | 6 +++++- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/frontend/src/routes/(authed)/(share)/create-from-share/[shareId]/+page.svelte b/apps/frontend/src/routes/(authed)/(share)/create-from-share/[shareId]/+page.svelte index ac81bfe87..2d4cc04cf 100644 --- a/apps/frontend/src/routes/(authed)/(share)/create-from-share/[shareId]/+page.svelte +++ b/apps/frontend/src/routes/(authed)/(share)/create-from-share/[shareId]/+page.svelte @@ -99,8 +99,8 @@
- Create from template - Create a new base from a template. + Create from share + Create a new base from a shared base.
diff --git a/apps/frontend/src/routes/(share)/s/b/[shareId]/+layout.svelte b/apps/frontend/src/routes/(share)/s/b/[shareId]/+layout.svelte index 148e63e04..08b972a94 100644 --- a/apps/frontend/src/routes/(share)/s/b/[shareId]/+layout.svelte +++ b/apps/frontend/src/routes/(share)/s/b/[shareId]/+layout.svelte @@ -4,6 +4,8 @@ import type { PaneAPI } from "paneforge" import type { LayoutData } from "./$types" import Logo from "$lib/images/logo.svg" + import { Button } from "$lib/components/ui/button" + import { page } from "$app/stores" export let data: LayoutData @@ -34,13 +36,7 @@ Undb - +
diff --git a/packages/command-handlers/src/handlers/create-from-share.command-handler.ts b/packages/command-handlers/src/handlers/create-from-share.command-handler.ts index 272e0cc3b..cd30d345c 100644 --- a/packages/command-handlers/src/handlers/create-from-share.command-handler.ts +++ b/packages/command-handlers/src/handlers/create-from-share.command-handler.ts @@ -8,6 +8,7 @@ import { type ICommandHandler } from "@undb/domain" import { createLogger } from "@undb/logger" import { injectShareRepository, WithShareId, type IShareRepository } from "@undb/share" import { injectTableService, type ITableService } from "@undb/table" +import { getNextName } from "@undb/utils" @commandHandler(CreateFromShareCommand) @singleton() @@ -44,12 +45,15 @@ export class CreateFromShareCommandHandler implements ICommandHandler base.name.value) + const spec = new WithBaseId(new BaseId(baseId)).and(new WithBaseSpaceId(spaceId)) const base = (await this.baseRepository.findOne(spec)).expect("Base not found") const duplicatedBase = await this.tableService.duplicateBase(base, spaceId, targetSpaceId, { id: baseId, - name: command.name, + name: getNextName(baseNames, command.name), includeData: command.includeData, }) From 16f233bb96dfc6e806857d79bc4427281f7d1440 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Wed, 9 Oct 2024 17:45:20 +0800 Subject: [PATCH 2/5] chore: context injection --- apps/backend/src/modules/file/file.ts | 8 ++++--- apps/backend/src/modules/openapi/openapi.ts | 7 ++++-- .../backend/src/modules/space/space.module.ts | 7 ++++-- apps/backend/src/registry/db.registry.ts | 3 +++ .../create-api-token.command-handler.ts | 6 +++-- .../handlers/create-base.command-handler.ts | 7 ++++-- .../create-from-share.command-handler.ts | 8 ++++--- .../create-from-template.command-handler.ts | 8 ++++--- .../handlers/create-space.command-handler.ts | 6 +++-- .../handlers/create-table.command-handler.ts | 6 +++-- .../handlers/delete-space.command-handler.ts | 8 ++++--- .../duplicate-base.command-handler.ts | 6 +++-- .../src/handlers/invite.command-handler.ts | 7 ++++-- .../handlers/update-base.command-handler.ts | 7 ++++-- .../handlers/update-space.command-handler.ts | 6 +++-- packages/context/src/context.type.ts | 12 ++++++++++ packages/context/src/server.ts | 16 ++++++++++++- packages/graphql/src/index.ts | 24 ++++++++++++++----- .../src/audit/audit.filter-visitor.ts | 9 +++---- .../src/audit/audit.query-repository.ts | 5 +++- .../persistence/src/audit/audit.repository.ts | 8 ++++--- .../src/base/base.outbox-service.ts | 9 +++++-- .../persistence/src/base/base.repository.ts | 8 +++++-- .../src/member/invitation.filter-visitor.ts | 5 ++-- .../src/member/invitation.query-repository.ts | 7 ++++-- .../src/member/invitation.repository.ts | 10 +++++--- packages/persistence/src/outbox.mapper.ts | 8 +++---- .../src/record/record-query.helper.ts | 5 +++- .../src/record/record.filter-visitor.ts | 9 +++---- .../src/record/record.mutate-visitor.ts | 13 +++++----- .../src/record/record.outbox-service.ts | 11 ++++----- .../src/record/record.repository.ts | 17 +++++++------ .../persistence/src/share/share.mapper.ts | 9 +++++-- .../persistence/src/space/space.repository.ts | 10 ++++---- .../src/table/table-db.query-spec-handler.ts | 3 ++- .../src/table/table.filter-visitor.ts | 5 ++-- .../src/table/table.outbox-service.ts | 9 +++++-- .../src/table/table.query-repository.ts | 9 ++++--- .../persistence/src/table/table.repository.ts | 13 ++++++---- .../underlying-table-spec.visitor.ts | 5 ++-- .../underlying/underlying-table.service.ts | 9 ++++--- .../src/webhook/webhook.filter-visitor.ts | 5 ++-- .../persistence/src/webhook/webhook.mapper.ts | 8 +++++-- .../src/webhook/webhook.query-repository.ts | 5 +++- .../src/webhook/webhook.repository.ts | 5 +++- .../handlers/get-api-tokens.query-handler.ts | 7 ++++-- .../get-base-by-share.query-handler.ts | 7 ++++-- .../src/handlers/get-bases.query-handler.ts | 7 ++++-- .../src/handlers/get-share.query-handler.ts | 7 ++++-- .../get-table-by-share-table.query-handler.ts | 7 ++++-- .../get-table-by-share.query-handler.ts | 7 ++++-- 51 files changed, 281 insertions(+), 132 deletions(-) diff --git a/apps/backend/src/modules/file/file.ts b/apps/backend/src/modules/file/file.ts index dbb7fb479..69ea52606 100644 --- a/apps/backend/src/modules/file/file.ts +++ b/apps/backend/src/modules/file/file.ts @@ -1,4 +1,4 @@ -import { getCurrentUserId, mustGetCurrentSpaceId } from "@undb/context/server" +import { type IContext, injectContext } from "@undb/context" import { singleton } from "@undb/di" import { injectQueryBuilder, type IQueryBuilder } from "@undb/persistence" import { injectObjectStorage, type IObjectStorage, type IPutObject } from "@undb/table" @@ -11,6 +11,8 @@ export class FileService { private readonly objectStorage: IObjectStorage, @injectQueryBuilder() private readonly qb: IQueryBuilder, + @injectContext() + private readonly context: IContext, ) {} async #uploadFile(buffer: Buffer, path: string, originalname: string, mimeType: string) { @@ -48,8 +50,8 @@ export class FileService { size, mime_type: mimeType, created_at: new Date().getTime(), - created_by: getCurrentUserId(), - space_id: mustGetCurrentSpaceId(), + created_by: this.context.mustGetCurrentUserId(), + space_id: this.context.mustGetCurrentSpaceId(), name: fileName, }) .execute() diff --git a/apps/backend/src/modules/openapi/openapi.ts b/apps/backend/src/modules/openapi/openapi.ts index 24d4139e8..ff9e6a924 100644 --- a/apps/backend/src/modules/openapi/openapi.ts +++ b/apps/backend/src/modules/openapi/openapi.ts @@ -1,6 +1,7 @@ import { injectSpaceMemberService, type ISpaceMemberService } from "@undb/authz" import { type IBaseRepository, injectBaseRepository } from "@undb/base" -import { executionContext, getCurrentUserId, setContextValue } from "@undb/context/server" +import { type IContext, injectContext } from "@undb/context" +import { executionContext, setContextValue } from "@undb/context/server" import { container, singleton } from "@undb/di" import { Some } from "@undb/domain" import { createLogger } from "@undb/logger" @@ -42,6 +43,8 @@ export class OpenAPI { private spaceMemberService: ISpaceMemberService, @injectSpaceService() private spaceService: ISpaceService, + @injectContext() + private readonly context: IContext, ) {} async getSpec(baseName: string, tableName: string) { @@ -149,7 +152,7 @@ export class OpenAPI { return } } else { - const userId = getCurrentUserId() + const userId = this.context.getCurrentUserId() if (userId) { return diff --git a/apps/backend/src/modules/space/space.module.ts b/apps/backend/src/modules/space/space.module.ts index 75d062b40..4df4aeb16 100644 --- a/apps/backend/src/modules/space/space.module.ts +++ b/apps/backend/src/modules/space/space.module.ts @@ -1,6 +1,7 @@ import { checkPermission } from "@undb/authz" import { DeleteSpaceCommand } from "@undb/commands" -import { getCurrentMember, getCurrentUserId } from "@undb/context/server" +import { type IContext, injectContext } from "@undb/context" +import { getCurrentMember } from "@undb/context/server" import { CommandBus } from "@undb/cqrs" import { inject, singleton } from "@undb/di" import { injectQueryBuilder, type IQueryBuilder } from "@undb/persistence" @@ -21,6 +22,8 @@ export class SpaceModule { private readonly commandBus: CommandBus, @injectQueryBuilder() private readonly qb: IQueryBuilder, + @injectContext() + private readonly context: IContext, ) {} public route() { return new Elysia() @@ -66,7 +69,7 @@ export class SpaceModule { return withTransaction(this.qb)(async () => { await this.commandBus.execute(new DeleteSpaceCommand({})) - const userId = getCurrentUserId() + const userId = this.context.mustGetCurrentUserId() await this.lucia.invalidateSession(userId) const space = (await this.spaceService.getSpace({ userId })).expect("Space not found") diff --git a/apps/backend/src/registry/db.registry.ts b/apps/backend/src/registry/db.registry.ts index 37a46d0bb..0263466b9 100644 --- a/apps/backend/src/registry/db.registry.ts +++ b/apps/backend/src/registry/db.registry.ts @@ -8,6 +8,8 @@ import { WORKSPQACE_MEMBER_QUERY_REPOSITORY, } from "@undb/authz" import { BASE_OUTBOX_SERVICE, BASE_QUERY_REPOSITORY, BASE_REPOSITORY } from "@undb/base" +import { CONTEXT_TOKEN } from "@undb/context" +import { ServerContext } from "@undb/context/server" import { container, instanceCachingFactory } from "@undb/di" import { env } from "@undb/env" import { API_TOKEN_QUERY_REPOSITORY, API_TOKEN_REPOSITORY, API_TOKEN_SERVICE, ApiTokenService } from "@undb/openapi" @@ -79,6 +81,7 @@ export const registerDb = () => { }), }) + container.register(CONTEXT_TOKEN, ServerContext) container.register(SPACE_REPOSITORY, SpaceRepostitory) container.register(SPACE_QUERY_REPOSITORY, SpaceQueryRepository) container.register(SPACE_SERVICE, SpaceService) diff --git a/packages/command-handlers/src/handlers/create-api-token.command-handler.ts b/packages/command-handlers/src/handlers/create-api-token.command-handler.ts index 137aa0428..775bb87aa 100644 --- a/packages/command-handlers/src/handlers/create-api-token.command-handler.ts +++ b/packages/command-handlers/src/handlers/create-api-token.command-handler.ts @@ -1,5 +1,5 @@ import { CreateApiTokenCommand } from "@undb/commands" -import { mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" import { commandHandler } from "@undb/cqrs" import { singleton } from "@undb/di" import type { ICommandHandler } from "@undb/domain" @@ -14,12 +14,14 @@ export class CreateApiTokenCommandHandler implements ICommandHandler { this.logger.debug(command) - const spaceId = mustGetCurrentSpaceId() + const spaceId = this.context.mustGetCurrentSpaceId() const token = ApiTokenDo.create({ ...command, spaceId }) await this.repository.insert(token) diff --git a/packages/command-handlers/src/handlers/create-base.command-handler.ts b/packages/command-handlers/src/handlers/create-base.command-handler.ts index 48bd30388..1ac63e96a 100644 --- a/packages/command-handlers/src/handlers/create-base.command-handler.ts +++ b/packages/command-handlers/src/handlers/create-base.command-handler.ts @@ -8,7 +8,7 @@ import { type IBaseRepository, } from "@undb/base" import { CreateBaseCommand } from "@undb/commands" -import { mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" import { commandHandler } from "@undb/cqrs" import { singleton } from "@undb/di" import { applyRules, type ICommandHandler } from "@undb/domain" @@ -22,12 +22,15 @@ export class CreateBaseCommandHandler implements ICommandHandler { this.logger.debug(command) - const nameSpec = new WithBaseName(BaseName.from(command.name)).and(new WithBaseSpaceId(mustGetCurrentSpaceId())) + const spaceId = this.context.mustGetCurrentSpaceId() + const nameSpec = new WithBaseName(BaseName.from(command.name)).and(new WithBaseSpaceId(spaceId)) const exists = (await this.repository.findOne(nameSpec)).into(null) applyRules(new BaseNameShouldBeUnique(!!exists)) diff --git a/packages/command-handlers/src/handlers/create-from-share.command-handler.ts b/packages/command-handlers/src/handlers/create-from-share.command-handler.ts index cd30d345c..888585e4e 100644 --- a/packages/command-handlers/src/handlers/create-from-share.command-handler.ts +++ b/packages/command-handlers/src/handlers/create-from-share.command-handler.ts @@ -1,7 +1,7 @@ import { checkPermission, injectSpaceMemberService, type ISpaceMemberService } from "@undb/authz" import { BaseId, injectBaseRepository, WithBaseId, WithBaseSpaceId, type IBaseRepository } from "@undb/base" import { CreateFromShareCommand } from "@undb/commands" -import { getCurrentUserId, mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" import { commandHandler } from "@undb/cqrs" import { singleton } from "@undb/di" import { type ICommandHandler } from "@undb/domain" @@ -24,6 +24,8 @@ export class CreateFromShareCommandHandler implements ICommandHandler { @@ -39,8 +41,8 @@ export class CreateFromShareCommandHandler implements ICommandHandler { - const spaceId = command.spaceId ?? mustGetCurrentSpaceId() - const userId = getCurrentUserId() + const spaceId = command.spaceId ?? this.context.mustGetCurrentSpaceId() + const userId = this.context.mustGetCurrentUserId() const member = (await this.spaceMemberService.getSpaceMember(userId, spaceId)).expect("Member not found") checkPermission(member.props.role, ["base:create"]) diff --git a/packages/command-handlers/src/handlers/create-space.command-handler.ts b/packages/command-handlers/src/handlers/create-space.command-handler.ts index af832bef6..456e8a82a 100644 --- a/packages/command-handlers/src/handlers/create-space.command-handler.ts +++ b/packages/command-handlers/src/handlers/create-space.command-handler.ts @@ -1,6 +1,6 @@ import { injectSpaceMemberService, type ISpaceMemberService } from "@undb/authz" import { CreateSpaceCommand } from "@undb/commands" -import { getCurrentUserId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" import { commandHandler } from "@undb/cqrs" import { singleton } from "@undb/di" import { type ICommandHandler } from "@undb/domain" @@ -17,6 +17,8 @@ export class CreateSpaceCommandHandler implements ICommandHandler { @@ -25,7 +27,7 @@ export class CreateSpaceCommandHandler implements ICommandHandler { this.logger.debug(command) - const spaceId = mustGetCurrentSpaceId() + const spaceId = this.context.mustGetCurrentSpaceId() const table = await this.service.createTable({ ...command, spaceId }) return table.id.value diff --git a/packages/command-handlers/src/handlers/delete-space.command-handler.ts b/packages/command-handlers/src/handlers/delete-space.command-handler.ts index ebf09844f..4e84bcd35 100644 --- a/packages/command-handlers/src/handlers/delete-space.command-handler.ts +++ b/packages/command-handlers/src/handlers/delete-space.command-handler.ts @@ -1,5 +1,5 @@ import { DeleteSpaceCommand } from "@undb/commands" -import { getCurrentUserId, mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" import { commandHandler } from "@undb/cqrs" import { singleton } from "@undb/di" import { type ICommandHandler } from "@undb/domain" @@ -11,15 +11,17 @@ export class DeleteSpaceCommandHandler implements ICommandHandler { - const spaces = await this.repository.find(new WithSpaceUserId(getCurrentUserId())) + const spaces = await this.repository.find(new WithSpaceUserId(this.context.mustGetCurrentUserId())) if (spaces.length === 1) { throw new Error("Cannot delete the last space") } - const spaceId = mustGetCurrentSpaceId() + const spaceId = this.context.mustGetCurrentSpaceId() const space = (await this.repository.findOneById(spaceId)).expect("Space not found") if (space.isPersonal) { throw new Error("Cannot delete personal space") diff --git a/packages/command-handlers/src/handlers/duplicate-base.command-handler.ts b/packages/command-handlers/src/handlers/duplicate-base.command-handler.ts index a40bd04e7..d3f3e16b6 100644 --- a/packages/command-handlers/src/handlers/duplicate-base.command-handler.ts +++ b/packages/command-handlers/src/handlers/duplicate-base.command-handler.ts @@ -1,6 +1,6 @@ import { injectBaseRepository, type IBaseRepository } from "@undb/base" import { DuplicateBaseCommand } from "@undb/commands" -import { mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" import { commandHandler } from "@undb/cqrs" import { singleton } from "@undb/di" import { type ICommandHandler } from "@undb/domain" @@ -17,12 +17,14 @@ export class DuplicateBaseCommandHandler implements ICommandHandler { const base = (await this.baseRepository.findOneById(command.id)).expect("Base not found") - const spaceId = mustGetCurrentSpaceId() + const spaceId = this.context.mustGetCurrentSpaceId() const duplicatedBase = await this.tableService.duplicateBase(base, spaceId, spaceId, command) return duplicatedBase.id.value diff --git a/packages/command-handlers/src/handlers/invite.command-handler.ts b/packages/command-handlers/src/handlers/invite.command-handler.ts index 303d29bf8..f0681b358 100644 --- a/packages/command-handlers/src/handlers/invite.command-handler.ts +++ b/packages/command-handlers/src/handlers/invite.command-handler.ts @@ -1,6 +1,7 @@ import { injectSpaceMemberService, type ISpaceMemberService } from "@undb/authz" import { InviteCommand } from "@undb/commands" -import { getCurrentUser, mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" +import { getCurrentUser } from "@undb/context/server" import { commandHandler } from "@undb/cqrs" import { singleton } from "@undb/di" import type { ICommandHandler } from "@undb/domain" @@ -13,11 +14,13 @@ export class InviteCommandHandler implements ICommandHandler constructor( @injectSpaceMemberService() private readonly service: ISpaceMemberService, + @injectContext() + private readonly context: IContext, ) {} async execute(command: InviteCommand): Promise { const user = getCurrentUser() - const spaceId = mustGetCurrentSpaceId() + const spaceId = this.context.mustGetCurrentSpaceId() await this.service.invite({ ...command, inviterId: user.userId!, spaceId }, user.username!) } diff --git a/packages/command-handlers/src/handlers/update-base.command-handler.ts b/packages/command-handlers/src/handlers/update-base.command-handler.ts index 386b8b5ad..d153aa2b4 100644 --- a/packages/command-handlers/src/handlers/update-base.command-handler.ts +++ b/packages/command-handlers/src/handlers/update-base.command-handler.ts @@ -9,7 +9,7 @@ import { type IBaseRepository, } from "@undb/base" import { UpdateBaseCommand } from "@undb/commands" -import { mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" import { commandHandler } from "@undb/cqrs" import { singleton } from "@undb/di" import { applyRules, type ICommandHandler } from "@undb/domain" @@ -23,6 +23,8 @@ export class UpdateBaseCommandHandler implements ICommandHandler { @@ -30,9 +32,10 @@ export class UpdateBaseCommandHandler implements ICommandHandler { - const spaceId = mustGetCurrentSpaceId() + const spaceId = this.context.mustGetCurrentSpaceId() const space = (await this.repository.findOneById(spaceId)).expect("Space not found") const spec = space.$update(command) diff --git a/packages/context/src/context.type.ts b/packages/context/src/context.type.ts index 9046bbaad..be97d8b3a 100644 --- a/packages/context/src/context.type.ts +++ b/packages/context/src/context.type.ts @@ -1,3 +1,5 @@ +import { inject } from "@undb/di" + interface ContextUser { userId: string | null username?: string @@ -19,3 +21,13 @@ export interface ExecuteContext { } export type SetContextValue = (key: keyof ExecuteContext, value: any) => void + +export interface IContext { + mustGetCurrentSpaceId(): string + mustGetCurrentUserId(): string + getCurrentUserId(): string | undefined +} + +export const CONTEXT_TOKEN = Symbol.for("context") + +export const injectContext = () => inject(CONTEXT_TOKEN) diff --git a/packages/context/src/server.ts b/packages/context/src/server.ts index f60a58307..d66b9630f 100644 --- a/packages/context/src/server.ts +++ b/packages/context/src/server.ts @@ -1,5 +1,6 @@ +import { singleton } from "@undb/di" import { AsyncLocalStorage } from "node:async_hooks" -import type { ExecuteContext, IExecutionContext, SetContextValue } from "." +import type { ExecuteContext, IContext, IExecutionContext, SetContextValue } from "." export const executionContext = new AsyncLocalStorage() satisfies IExecutionContext @@ -38,3 +39,16 @@ export const mustGetCurrentSpaceId = () => { return spaceId } + +@singleton() +export class ServerContext implements IContext { + mustGetCurrentSpaceId(): string { + return mustGetCurrentSpaceId() + } + mustGetCurrentUserId(): string { + return getCurrentUserId() + } + getCurrentUserId(): string | undefined { + return getCurrentUserId() + } +} diff --git a/packages/graphql/src/index.ts b/packages/graphql/src/index.ts index ef2f5a52b..d6e94d9d5 100644 --- a/packages/graphql/src/index.ts +++ b/packages/graphql/src/index.ts @@ -2,7 +2,8 @@ import { yoga } from "@elysiajs/graphql-yoga" import { useOpenTelemetry } from "@envelop/opentelemetry" import * as otel from "@opentelemetry/api" import type { ISpaceMemberDTO } from "@undb/authz" -import { executionContext, getCurrentSpaceId, getCurrentUserId, mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" +import { executionContext, getCurrentSpaceId } from "@undb/context/server" import { QueryBus } from "@undb/cqrs" import { inject, singleton } from "@undb/di" import type { Option } from "@undb/domain" @@ -55,6 +56,8 @@ export class Graphql { public readonly shareService: IShareService, @injectObjectStorage() public readonly objectStorage: IObjectStorage, + @injectContext() + public readonly context: IContext, ) {} public route() { @@ -398,7 +401,7 @@ export class Graphql { return space.unwrap() }, spaces: async () => { - const userId = getCurrentUserId() + const userId = this.context.getCurrentUserId() if (!userId) { return [] } @@ -489,7 +492,10 @@ export class Graphql { // @ts-ignore share: async (base) => { return ( - await this.shareService.getShareByTarget({ type: "base", id: base.id }, mustGetCurrentSpaceId()) + await this.shareService.getShareByTarget( + { type: "base", id: base.id }, + this.context.mustGetCurrentSpaceId(), + ) ).into(null) }, }, @@ -519,7 +525,10 @@ export class Graphql { // @ts-ignore share: async (view) => { return ( - await this.shareService.getShareByTarget({ type: "view", id: view.id }, mustGetCurrentSpaceId()) + await this.shareService.getShareByTarget( + { type: "view", id: view.id }, + this.context.mustGetCurrentSpaceId(), + ) ).into(null) }, }, @@ -527,7 +536,10 @@ export class Graphql { // @ts-ignore share: async (form) => { return ( - await this.shareService.getShareByTarget({ type: "form", id: form.id }, mustGetCurrentSpaceId()) + await this.shareService.getShareByTarget( + { type: "form", id: form.id }, + this.context.mustGetCurrentSpaceId(), + ) ).into(null) }, }, @@ -540,7 +552,7 @@ export class Graphql { Space: { // @ts-ignore member: async (space) => { - const userId = getCurrentUserId() + const userId = this.context.getCurrentUserId() if (!userId) { return null } diff --git a/packages/persistence/src/audit/audit.filter-visitor.ts b/packages/persistence/src/audit/audit.filter-visitor.ts index d410ceffc..9f23bd51c 100644 --- a/packages/persistence/src/audit/audit.filter-visitor.ts +++ b/packages/persistence/src/audit/audit.filter-visitor.ts @@ -10,15 +10,16 @@ import type { WithAuditTimestamp, } from "@undb/audit" import type { WithAuditRecordId } from "@undb/audit/src/specifications/audit-record-id.specification" -import { mustGetCurrentSpaceId } from "@undb/context/server" import type { ExpressionBuilder } from "kysely" import { AbstractQBVisitor } from "../abstract-qb.visitor" import type { Database } from "../db" export class AuditFilterVisitor extends AbstractQBVisitor implements IAuditSpecVisitor { - constructor(protected readonly eb: ExpressionBuilder) { + constructor( + protected readonly eb: ExpressionBuilder, + private readonly spaceId: string, + ) { super(eb) - const spaceId = mustGetCurrentSpaceId() this.addCond(this.eb.eb("space_id", "=", spaceId)) } idEqual(s: WithAuditId): void { @@ -47,6 +48,6 @@ export class AuditFilterVisitor extends AbstractQBVisitor implements IAud throw new Error("Method not implemented.") } clone(): this { - return new AuditFilterVisitor(this.eb) as this + return new AuditFilterVisitor(this.eb, this.spaceId) as this } } diff --git a/packages/persistence/src/audit/audit.query-repository.ts b/packages/persistence/src/audit/audit.query-repository.ts index a6cc2895b..dd19c1de7 100644 --- a/packages/persistence/src/audit/audit.query-repository.ts +++ b/packages/persistence/src/audit/audit.query-repository.ts @@ -1,4 +1,5 @@ import type { AuditSpecification, IAuditDTO, IAuditQueryRepository } from "@undb/audit" +import { injectContext, type IContext } from "@undb/context" import { inject, singleton } from "@undb/di" import type { IQueryBuilder } from "../qb" import { injectQueryBuilder } from "../qb.provider" @@ -12,6 +13,8 @@ export class AuditQueryRepository implements IAuditQueryRepository { private readonly mapper: AuditMapper, @injectQueryBuilder() private readonly qb: IQueryBuilder, + @injectContext() + private readonly context: IContext, ) {} async find(spec: AuditSpecification): Promise { @@ -19,7 +22,7 @@ export class AuditQueryRepository implements IAuditQueryRepository { .selectFrom("undb_audit") .selectAll() .where((eb) => { - const visitor = new AuditFilterVisitor(eb) + const visitor = new AuditFilterVisitor(eb, this.context.mustGetCurrentSpaceId()) spec.accept(visitor) return visitor.cond }) diff --git a/packages/persistence/src/audit/audit.repository.ts b/packages/persistence/src/audit/audit.repository.ts index f6e469399..ed5e2685c 100644 --- a/packages/persistence/src/audit/audit.repository.ts +++ b/packages/persistence/src/audit/audit.repository.ts @@ -1,5 +1,5 @@ import type { Audit, AuditSpecification, IAuditRepository } from "@undb/audit" -import { getCurrentUserId, mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" import { inject, singleton } from "@undb/di" import type { Option } from "@undb/domain" import type { IQueryBuilder } from "../qb" @@ -13,19 +13,21 @@ export class AuditRepository implements IAuditRepository { private readonly mapper: AuditMapper, @injectQueryBuilder() private readonly qb: IQueryBuilder, + @injectContext() + private readonly context: IContext, ) {} findOne(spec: AuditSpecification): Promise> { throw new Error("Method not implemented.") } async insert(audit: Audit): Promise { - const user = getCurrentUserId() + const user = this.context.getCurrentUserId() const values = this.mapper.toEntity(audit) await this.qb .insertInto("undb_audit") .values({ ...values, - space_id: mustGetCurrentSpaceId(), + space_id: this.context.mustGetCurrentSpaceId(), operator_id: user, }) .execute() diff --git a/packages/persistence/src/base/base.outbox-service.ts b/packages/persistence/src/base/base.outbox-service.ts index 0ea1fecfb..b34e51d76 100644 --- a/packages/persistence/src/base/base.outbox-service.ts +++ b/packages/persistence/src/base/base.outbox-service.ts @@ -1,19 +1,24 @@ import type { Base, IBaseOutboxService } from "@undb/base" +import { injectContext, type IContext } from "@undb/context" import { singleton } from "@undb/di" import { getCurrentTransaction } from "../ctx" import { OutboxMapper } from "../outbox.mapper" @singleton() export class BaseOutboxService implements IBaseOutboxService { + constructor( + @injectContext() + private readonly context: IContext, + ) {} async save(r: Base): Promise { - const values = r.domainEvents.map(OutboxMapper.fromEvent) + const values = r.domainEvents.map((e) => OutboxMapper.fromEvent(e, this.context)) if (!values.length) return await getCurrentTransaction().insertInto("undb_outbox").values(values).execute() r.removeEvents(r.domainEvents) } async saveMany(d: Base[]): Promise { - const values = d.flatMap((r) => r.domainEvents.map(OutboxMapper.fromEvent)) + const values = d.flatMap((r) => r.domainEvents.map((e) => OutboxMapper.fromEvent(e, this.context))) if (!values.length) return await getCurrentTransaction().insertInto("undb_outbox").values(values).execute() diff --git a/packages/persistence/src/base/base.repository.ts b/packages/persistence/src/base/base.repository.ts index b81d572fb..0795dd3fa 100644 --- a/packages/persistence/src/base/base.repository.ts +++ b/packages/persistence/src/base/base.repository.ts @@ -7,7 +7,8 @@ import { type IBaseRepository, type IBaseSpecification, } from "@undb/base" -import { executionContext, mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" +import { executionContext } from "@undb/context/server" import { inject, singleton } from "@undb/di" import { None, Some, type Option } from "@undb/domain" import { injectTableRepository, TableBaseIdSpecification, type ITableRepository } from "@undb/table" @@ -32,6 +33,8 @@ export class BaseRepository implements IBaseRepository { private readonly tableRepository: ITableRepository, @inject(UnderlyingTableService) private readonly underlyingTableService: UnderlyingTableService, + @injectContext() + private readonly context: IContext, ) {} async find(spec: IBaseSpecification): Promise { @@ -62,7 +65,8 @@ export class BaseRepository implements IBaseRepository { return base ? Some(this.mapper.toDo(base)) : None } async findOneById(id: string): Promise> { - const spec = WithBaseId.fromString(id).and(new WithBaseSpaceId(mustGetCurrentSpaceId())) + const spaceId = this.context.mustGetCurrentSpaceId() + const spec = WithBaseId.fromString(id).and(new WithBaseSpaceId(spaceId)) const base = await (getCurrentTransaction() ?? this.qb) .selectFrom("undb_base") diff --git a/packages/persistence/src/member/invitation.filter-visitor.ts b/packages/persistence/src/member/invitation.filter-visitor.ts index 19865f202..ee27cce27 100644 --- a/packages/persistence/src/member/invitation.filter-visitor.ts +++ b/packages/persistence/src/member/invitation.filter-visitor.ts @@ -7,7 +7,6 @@ import type { WithSpaceId, WithStatus, } from "@undb/authz" -import { mustGetCurrentSpaceId } from "@undb/context/server" import type { ExpressionBuilder } from "kysely" import { AbstractQBVisitor } from "../abstract-qb.visitor" import type { Database } from "../db" @@ -15,11 +14,11 @@ import type { Database } from "../db" export class InvitationFilterVisitor extends AbstractQBVisitor implements IInvitationVisitor { constructor( protected readonly eb: ExpressionBuilder, + private readonly spaceId: string, cloned = false, ) { super(eb) if (!cloned) { - const spaceId = mustGetCurrentSpaceId() this.addCond(this.eb.eb("space_id", "=", spaceId)) } } @@ -47,6 +46,6 @@ export class InvitationFilterVisitor extends AbstractQBVisitor imp } clone(): this { - return new InvitationFilterVisitor(this.eb, true) as this + return new InvitationFilterVisitor(this.eb, this.spaceId, true) as this } } diff --git a/packages/persistence/src/member/invitation.query-repository.ts b/packages/persistence/src/member/invitation.query-repository.ts index 51f66daef..5b128ebab 100644 --- a/packages/persistence/src/member/invitation.query-repository.ts +++ b/packages/persistence/src/member/invitation.query-repository.ts @@ -1,4 +1,5 @@ import type { IInvitationQueryRepository, InvitationCompositeSpecification, InvitationDTO } from "@undb/authz" +import { injectContext, type IContext } from "@undb/context" import { singleton } from "@undb/di" import { None, Some, type Option } from "@undb/domain" import { getCurrentTransaction } from "../ctx" @@ -11,6 +12,8 @@ export class InvitationQueryRepository implements IInvitationQueryRepository { constructor( @injectQueryBuilder() private readonly qb: IQueryBuilder, + @injectContext() + private readonly context: IContext, ) {} async findOneById(id: string): Promise> { const invitation = await (getCurrentTransaction() ?? this.qb) @@ -37,7 +40,7 @@ export class InvitationQueryRepository implements IInvitationQueryRepository { .selectFrom("undb_invitation") .selectAll() .where((eb) => { - const visitor = new InvitationFilterVisitor(eb) + const visitor = new InvitationFilterVisitor(eb, this.context.mustGetCurrentSpaceId()) spec.accept(visitor) return visitor.cond @@ -62,7 +65,7 @@ export class InvitationQueryRepository implements IInvitationQueryRepository { .selectFrom("undb_invitation") .selectAll() .where((eb) => { - const visitor = new InvitationFilterVisitor(eb) + const visitor = new InvitationFilterVisitor(eb, this.context.mustGetCurrentSpaceId()) if (spec.isSome()) { spec.unwrap().accept(visitor) } diff --git a/packages/persistence/src/member/invitation.repository.ts b/packages/persistence/src/member/invitation.repository.ts index 74aae9139..4a92bfdb2 100644 --- a/packages/persistence/src/member/invitation.repository.ts +++ b/packages/persistence/src/member/invitation.repository.ts @@ -1,11 +1,15 @@ import type { IInvitationRepository, InvitationCompositeSpecification, InvitationDo } from "@undb/authz" -import { mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" import { singleton } from "@undb/di" import { getCurrentTransaction } from "../ctx" import { InvitationMutationVisitor } from "./invitation.mutation-visitor" @singleton() export class InvitationRepository implements IInvitationRepository { + constructor( + @injectContext() + private readonly context: IContext, + ) {} async deleteOneById(id: string): Promise { const trx = getCurrentTransaction() @@ -34,7 +38,7 @@ export class InvitationRepository implements IInvitationRepository { .values({ id: invitation.id.value, email: invitation.email, - space_id: mustGetCurrentSpaceId(), + space_id: this.context.mustGetCurrentSpaceId(), role: invitation.role, status: invitation.status, invited_at: invitation.invitedAt.getTime(), @@ -58,7 +62,7 @@ export class InvitationRepository implements IInvitationRepository { .values({ id: invitation.id.value, email: invitation.email, - space_id: mustGetCurrentSpaceId(), + space_id: this.context.mustGetCurrentSpaceId(), role: invitation.role, status: invitation.status, invited_at: invitation.invitedAt.getTime(), diff --git a/packages/persistence/src/outbox.mapper.ts b/packages/persistence/src/outbox.mapper.ts index 09f873447..7c065b8fd 100644 --- a/packages/persistence/src/outbox.mapper.ts +++ b/packages/persistence/src/outbox.mapper.ts @@ -1,12 +1,12 @@ -import { getCurrentUserId, mustGetCurrentSpaceId } from "@undb/context/server" +import type { IContext } from "@undb/context" import type { BaseEvent } from "@undb/domain" import type { InsertOutbox } from "./db" import { json } from "./qb" export class OutboxMapper { - static fromEvent(event: BaseEvent): InsertOutbox { - const user = getCurrentUserId() - const spaceId = event.spaceId ?? mustGetCurrentSpaceId() + static fromEvent(event: BaseEvent, context: IContext): InsertOutbox { + const user = context.getCurrentUserId() + const spaceId = event.spaceId ?? context.mustGetCurrentSpaceId() return { id: event.id, name: event.name, diff --git a/packages/persistence/src/record/record-query.helper.ts b/packages/persistence/src/record/record-query.helper.ts index 6efeb5eb4..7d0274aa2 100644 --- a/packages/persistence/src/record/record-query.helper.ts +++ b/packages/persistence/src/record/record-query.helper.ts @@ -1,3 +1,4 @@ +import { injectContext, type IContext } from "@undb/context" import { singleton } from "@undb/di" import type { IPagination, Option } from "@undb/domain" import { FieldIdVo, type Field, type IViewSort, type RecordComositeSpecification, type TableDo } from "@undb/table" @@ -18,6 +19,8 @@ export class RecordQueryHelper { constructor( @injectQueryBuilder() public readonly qb: IRecordQueryBuilder, + @injectContext() + private readonly context: IContext, ) {} createQueryCreator( @@ -62,7 +65,7 @@ export class RecordQueryHelper { handleWhere(table: TableDo, spec: Option) { return (eb: ExpressionBuilder) => { - const visitor = new RecordFilterVisitor(eb, table) + const visitor = new RecordFilterVisitor(eb, table, this.context) if (spec?.isSome()) { spec.unwrap().accept(visitor) } diff --git a/packages/persistence/src/record/record.filter-visitor.ts b/packages/persistence/src/record/record.filter-visitor.ts index bfada0d4f..76fcc618a 100644 --- a/packages/persistence/src/record/record.filter-visitor.ts +++ b/packages/persistence/src/record/record.filter-visitor.ts @@ -1,4 +1,4 @@ -import { getCurrentUserId } from "@undb/context/server" +import type { IContext } from "@undb/context" import { NotImplementException } from "@undb/domain" import { CurrencyGT, @@ -74,6 +74,7 @@ export class RecordFilterVisitor extends AbstractQBVisitor implements constructor( eb: ExpressionBuilder, private readonly table: TableDo, + private readonly context: IContext, ) { super(eb) } @@ -137,10 +138,10 @@ export class RecordFilterVisitor extends AbstractQBVisitor implements const cond = this.eb.eb(this.getFieldId(spec), "is", null) this.addCond(cond) } else { - function convertMacro(value: string) { + const convertMacro = (value: string) => { if (isUserFieldMacro(value)) { if (value === "@me") { - return getCurrentUserId() + return this.context.getCurrentUserId() } } @@ -328,6 +329,6 @@ export class RecordFilterVisitor extends AbstractQBVisitor implements this.addCond(cond) } clone(): this { - return new RecordFilterVisitor(this.eb, this.table) as this + return new RecordFilterVisitor(this.eb, this.table, this.context) as this } } diff --git a/packages/persistence/src/record/record.mutate-visitor.ts b/packages/persistence/src/record/record.mutate-visitor.ts index 6aa7695f4..acc7df2b0 100644 --- a/packages/persistence/src/record/record.mutate-visitor.ts +++ b/packages/persistence/src/record/record.mutate-visitor.ts @@ -1,4 +1,4 @@ -import { getCurrentUserId, mustGetCurrentSpaceId } from "@undb/context/server" +import type { IContext } from "@undb/context" import { CurrencyEqual, CurrencyGT, @@ -71,6 +71,7 @@ export class RecordMutateVisitor extends AbstractQBMutationVisitor implements IR private readonly record: RecordDO | null, private readonly qb: IRecordQueryBuilder, private readonly eb: ExpressionBuilder, + private readonly context: IContext, ) { super() } @@ -116,8 +117,8 @@ export class RecordMutateVisitor extends AbstractQBMutationVisitor implements IR this.addSql(deleteSql) if (s.value?.length) { - const userId = getCurrentUserId() - const spaceId = mustGetCurrentSpaceId() + const userId = this.context.getCurrentUserId() + const spaceId = this.context.mustGetCurrentSpaceId() const insert = (this.qb as IQueryBuilder) .insertInto("undb_attachment") .values( @@ -238,10 +239,10 @@ export class RecordMutateVisitor extends AbstractQBMutationVisitor implements IR const fieldValue = new UserFieldValue(spec.value) const value = fieldValue.getValue(field) - function convertMacro(value: string) { + const convertMacro = (value: string) => { if (isUserFieldMacro(value)) { if (value === "@me") { - return getCurrentUserId() + return this.context.getCurrentUserId() } } @@ -366,6 +367,6 @@ export class RecordMutateVisitor extends AbstractQBMutationVisitor implements IR this.setData(s.fieldId.value, s.value || null) } clone(): this { - return new RecordMutateVisitor(this.table, this.record, this.qb, this.eb) as this + return new RecordMutateVisitor(this.table, this.record, this.qb, this.eb, this.context) as this } } diff --git a/packages/persistence/src/record/record.outbox-service.ts b/packages/persistence/src/record/record.outbox-service.ts index d4d7d8312..2821ac51c 100644 --- a/packages/persistence/src/record/record.outbox-service.ts +++ b/packages/persistence/src/record/record.outbox-service.ts @@ -1,20 +1,19 @@ +import { injectContext, type IContext } from "@undb/context" import { singleton } from "@undb/di" import type { IRecordOutboxService, RecordDO } from "@undb/table" import { getCurrentTransaction } from "../ctx" import { OutboxMapper } from "../outbox.mapper" -import type { IQueryBuilder } from "../qb" -import { injectQueryBuilder } from "../qb.provider" @singleton() export class RecordOutboxService implements IRecordOutboxService { constructor( - @injectQueryBuilder() - private readonly qb: IQueryBuilder, + @injectContext() + private readonly context: IContext, ) {} async save(r: RecordDO): Promise { const trx = getCurrentTransaction() - const values = r.domainEvents.map(OutboxMapper.fromEvent) + const values = r.domainEvents.map((e) => OutboxMapper.fromEvent(e, this.context)) if (!values.length) return await trx.insertInto("undb_outbox").values(values).execute() r.removeEvents(r.domainEvents) @@ -23,7 +22,7 @@ export class RecordOutboxService implements IRecordOutboxService { if (!d.length) return const trx = getCurrentTransaction() - const values = d.flatMap((r) => r.domainEvents.map(OutboxMapper.fromEvent)) + const values = d.flatMap((r) => r.domainEvents.map((e) => OutboxMapper.fromEvent(e, this.context))) await trx.insertInto("undb_outbox").values(values).execute() for (const r of d) { r.removeEvents(r.domainEvents) diff --git a/packages/persistence/src/record/record.repository.ts b/packages/persistence/src/record/record.repository.ts index 917f04d6a..78aa419d9 100644 --- a/packages/persistence/src/record/record.repository.ts +++ b/packages/persistence/src/record/record.repository.ts @@ -1,3 +1,4 @@ +import { injectContext, type IContext } from "@undb/context" import { executionContext } from "@undb/context/server" import { inject, singleton } from "@undb/di" import { None, Some, type Option } from "@undb/domain" @@ -21,8 +22,6 @@ import { import { chunk } from "es-toolkit/array" import { sql, type CompiledQuery, type ExpressionBuilder } from "kysely" import { getAnonymousTransaction } from "../ctx" -import type { IRecordQueryBuilder } from "../qb" -import { injectQueryBuilder } from "../qb.provider" import { UnderlyingTable } from "../underlying/underlying-table" import { RecordQueryHelper } from "./record-query.helper" import { getRecordDTOFromEntity } from "./record-utils" @@ -32,8 +31,6 @@ import { RecordMutateVisitor } from "./record.mutate-visitor" @singleton() export class RecordRepository implements IRecordRepository { constructor( - @injectQueryBuilder() - private readonly qb: IRecordQueryBuilder, @injectRecordOutboxService() private readonly outboxService: IRecordOutboxService, @inject(RecordMapper) @@ -42,6 +39,8 @@ export class RecordRepository implements IRecordRepository { private readonly tableRepo: ITableRepository, @inject(RecordQueryHelper) private readonly helper: RecordQueryHelper, + @injectContext() + private readonly context: IContext, ) {} private async getForeignTables(table: TableDo, fields: Field[]): Promise> { @@ -70,7 +69,7 @@ export class RecordRepository implements IRecordRepository { await trx .insertInto(t.name) .values((eb) => { - const visitor = new RecordMutateVisitor(table, record, trx, eb) + const visitor = new RecordMutateVisitor(table, record, trx, eb, this.context) spec.accept(visitor) sql.push(...visitor.sql) @@ -102,7 +101,7 @@ export class RecordRepository implements IRecordRepository { .values((eb) => records.map((record) => { const spec = record.toInsertSpec(table) - const visitor = new RecordMutateVisitor(table, record, trx, eb) + const visitor = new RecordMutateVisitor(table, record, trx, eb, this.context) spec.accept(visitor) sql.push(...visitor.sql) @@ -214,7 +213,7 @@ export class RecordRepository implements IRecordRepository { await trx .updateTable(t.name) .set((eb) => { - const visitor = new RecordMutateVisitor(table, record, trx, eb) + const visitor = new RecordMutateVisitor(table, record, trx, eb, this.context) spec.unwrap().accept(visitor) sql.push(...visitor.sql) return { ...visitor.data, [UPDATED_BY_TYPE]: userId } @@ -250,13 +249,13 @@ export class RecordRepository implements IRecordRepository { let data = {} if (records.length) { for (const record of records) { - const visitor = new RecordMutateVisitor(table, record, trx, eb) + const visitor = new RecordMutateVisitor(table, record, trx, eb, this.context) update.accept(visitor) queries.push(...visitor.sql) data = { ...data, ...visitor.data } } } else { - const visitor = new RecordMutateVisitor(table, null, trx, eb) + const visitor = new RecordMutateVisitor(table, null, trx, eb, this.context) update.accept(visitor) queries.push(...visitor.sql) data = visitor.data diff --git a/packages/persistence/src/share/share.mapper.ts b/packages/persistence/src/share/share.mapper.ts index 5bf7722b2..f2ca401e7 100644 --- a/packages/persistence/src/share/share.mapper.ts +++ b/packages/persistence/src/share/share.mapper.ts @@ -1,4 +1,4 @@ -import { mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" import { singleton } from "@undb/di" import type { Mapper } from "@undb/domain" import { ShareFactory, type IShareDTO, type IShareTarget, type Share as ShareDo } from "@undb/share" @@ -6,6 +6,11 @@ import type { Share } from "../db" @singleton() export class ShareMapper implements Mapper { + constructor( + @injectContext() + private readonly context: IContext, + ) {} + toDo(entity: Share): ShareDo { return ShareFactory.fromJSON({ id: entity.id, @@ -22,7 +27,7 @@ export class ShareMapper implements Mapper { id: domain.id.value, target_id: domain.target.id, target_type: domain.target.type, - space_id: mustGetCurrentSpaceId(), + space_id: this.context.mustGetCurrentSpaceId(), enabled: domain.enabled, } } diff --git a/packages/persistence/src/space/space.repository.ts b/packages/persistence/src/space/space.repository.ts index 093c820ff..20e51b9b8 100644 --- a/packages/persistence/src/space/space.repository.ts +++ b/packages/persistence/src/space/space.repository.ts @@ -1,4 +1,4 @@ -import { getCurrentUserId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" import { singleton } from "@undb/di" import { None, Some, type Option } from "@undb/domain" import { SpaceFactory, type ISpaceRepository, type ISpaceSpecification, type Space } from "@undb/space" @@ -13,6 +13,8 @@ export class SpaceRepostitory implements ISpaceRepository { constructor( @injectQueryBuilder() private readonly qb: IQueryBuilder, + @injectContext() + private readonly context: IContext, ) {} async find(spec: ISpaceSpecification): Promise { const space = await (getCurrentTransaction() ?? this.qb) @@ -77,7 +79,7 @@ export class SpaceRepostitory implements ISpaceRepository { } async insert(space: Space): Promise { const tx = getCurrentTransaction() - const userId = getCurrentUserId() + const userId = this.context.getCurrentUserId() await tx .insertInto("undb_space") .values({ @@ -96,7 +98,7 @@ export class SpaceRepostitory implements ISpaceRepository { const visitor = new SpaceMutateVisitor() spec.accept(visitor) - const userId = getCurrentUserId() + const userId = this.context.getCurrentUserId() await getCurrentTransaction() .updateTable("undb_space") .set({ ...visitor.data, updated_by: userId, updated_at: new Date().toISOString() }) @@ -108,7 +110,7 @@ export class SpaceRepostitory implements ISpaceRepository { await tx .updateTable("undb_space") - .set({ deleted_at: new Date().getTime(), deleted_by: getCurrentUserId() }) + .set({ deleted_at: new Date().getTime(), deleted_by: this.context.getCurrentUserId() }) .where("id", "=", id) .execute() } diff --git a/packages/persistence/src/table/table-db.query-spec-handler.ts b/packages/persistence/src/table/table-db.query-spec-handler.ts index 255b70601..ffa68f3be 100644 --- a/packages/persistence/src/table/table-db.query-spec-handler.ts +++ b/packages/persistence/src/table/table-db.query-spec-handler.ts @@ -8,11 +8,12 @@ export class TableDbQuerySpecHandler { constructor( private readonly qb: IQueryBuilder, private readonly eb: ExpressionBuilder, + private readonly spaceId: string, private readonly ignoreSpace = false, ) {} handle(spec: Option) { - const visitor = new TableFilterVisitor(this.qb, this.eb, this.ignoreSpace) + const visitor = new TableFilterVisitor(this.qb, this.eb, this.spaceId, this.ignoreSpace) if (spec.isSome()) { spec.unwrap().accept(visitor) } diff --git a/packages/persistence/src/table/table.filter-visitor.ts b/packages/persistence/src/table/table.filter-visitor.ts index 2a49a806a..238437a5f 100644 --- a/packages/persistence/src/table/table.filter-visitor.ts +++ b/packages/persistence/src/table/table.filter-visitor.ts @@ -1,4 +1,3 @@ -import { mustGetCurrentSpaceId } from "@undb/context/server" import type { DuplicatedTableSpecification, ITableSpecVisitor, @@ -44,12 +43,12 @@ export class TableFilterVisitor extends AbstractQBVisitor implements IT constructor( private readonly qb: IQueryBuilder, protected readonly eb: ExpressionBuilder, + private readonly spaceId: string, private readonly ignoreSpace = false, cloned = false, ) { super(eb) if (!ignoreSpace && !cloned) { - const spaceId = mustGetCurrentSpaceId() this.addCond(this.eb.eb("undb_table.space_id", "=", spaceId)) } } @@ -168,6 +167,6 @@ export class TableFilterVisitor extends AbstractQBVisitor implements IT } withTableUnqueName(spec: TableUniqueNameSpecification): void {} clone(): this { - return new TableFilterVisitor(this.qb, this.eb, this.ignoreSpace, true) as this + return new TableFilterVisitor(this.qb, this.eb, this.spaceId, this.ignoreSpace, true) as this } } diff --git a/packages/persistence/src/table/table.outbox-service.ts b/packages/persistence/src/table/table.outbox-service.ts index 464981b59..437cc583e 100644 --- a/packages/persistence/src/table/table.outbox-service.ts +++ b/packages/persistence/src/table/table.outbox-service.ts @@ -1,3 +1,4 @@ +import { injectContext, type IContext } from "@undb/context" import { singleton } from "@undb/di" import type { ITableOutboxService, TableDo } from "@undb/table" import { getCurrentTransaction } from "../ctx" @@ -5,9 +6,13 @@ import { OutboxMapper } from "../outbox.mapper" @singleton() export class TableOutboxService implements ITableOutboxService { + constructor( + @injectContext() + private readonly context: IContext, + ) {} async save(table: TableDo): Promise { const trx = getCurrentTransaction() - const values = table.domainEvents.map(OutboxMapper.fromEvent) + const values = table.domainEvents.map((e) => OutboxMapper.fromEvent(e, this.context)) if (!values.length) return await trx.insertInto("undb_outbox").values(values).execute() table.removeEvents(table.domainEvents) @@ -15,7 +20,7 @@ export class TableOutboxService implements ITableOutboxService { async saveMany(d: TableDo[]): Promise { const trx = getCurrentTransaction() - const values = d.flatMap((table) => table.domainEvents.map(OutboxMapper.fromEvent)) + const values = d.flatMap((table) => table.domainEvents.map((e) => OutboxMapper.fromEvent(e, this.context))) if (!values.length) return await trx.insertInto("undb_outbox").values(values).execute() d.forEach((table) => table.removeEvents(table.domainEvents)) diff --git a/packages/persistence/src/table/table.query-repository.ts b/packages/persistence/src/table/table.query-repository.ts index 665415a8c..7c328b25a 100644 --- a/packages/persistence/src/table/table.query-repository.ts +++ b/packages/persistence/src/table/table.query-repository.ts @@ -1,3 +1,4 @@ +import { injectContext, type IContext } from "@undb/context" import { singleton } from "@undb/di" import { None, Option, Some } from "@undb/domain" import { @@ -18,6 +19,8 @@ export class TableQueryRepository implements ITableQueryRepository { constructor( @injectQueryBuilder() private readonly qb: IQueryBuilder, + @injectContext() + private readonly context: IContext, ) {} public get mapper() { @@ -29,7 +32,7 @@ export class TableQueryRepository implements ITableQueryRepository { .selectFrom("undb_table") .selectAll() .$if(spec.isSome(), (qb) => new TableReferenceVisitor(qb).call(spec.unwrap())) - .where((eb) => new TableDbQuerySpecHandler(this.qb, eb).handle(spec)) + .where((eb) => new TableDbQuerySpecHandler(this.qb, eb, this.context.mustGetCurrentSpaceId()).handle(spec)) .execute() return tbs.map((r) => this.mapper.toDTO(r)) @@ -40,7 +43,7 @@ export class TableQueryRepository implements ITableQueryRepository { .selectFrom("undb_table") .selectAll() .$call((qb) => new TableReferenceVisitor(qb).call(spec)) - .where((eb) => new TableDbQuerySpecHandler(this.qb, eb).handle(Some(spec))) + .where((eb) => new TableDbQuerySpecHandler(this.qb, eb, this.context.mustGetCurrentSpaceId()).handle(Some(spec))) .executeTakeFirst() return tb ? Some(this.mapper.toDTO(tb)) : None @@ -52,7 +55,7 @@ export class TableQueryRepository implements ITableQueryRepository { .selectFrom("undb_table") .selectAll() .$call((qb) => new TableReferenceVisitor(qb).call(spec.unwrap())) - .where((eb) => new TableDbQuerySpecHandler(this.qb, eb).handle(spec)) + .where((eb) => new TableDbQuerySpecHandler(this.qb, eb, this.context.mustGetCurrentSpaceId()).handle(spec)) .executeTakeFirst() return tb ? Some(this.mapper.toDTO(tb)) : None diff --git a/packages/persistence/src/table/table.repository.ts b/packages/persistence/src/table/table.repository.ts index 1523177fe..6d8a47518 100644 --- a/packages/persistence/src/table/table.repository.ts +++ b/packages/persistence/src/table/table.repository.ts @@ -1,3 +1,4 @@ +import { injectContext, type IContext } from "@undb/context" import { executionContext, getCurrentSpaceId } from "@undb/context/server" import { inject, singleton } from "@undb/di" import { None, Option, Some } from "@undb/domain" @@ -30,6 +31,8 @@ export class TableRepository implements ITableRepository { private readonly outboxService: ITableOutboxService, @injectQueryBuilder() private readonly qb: IQueryBuilder, + @injectContext() + private readonly context: IContext, ) {} get mapper() { @@ -127,7 +130,9 @@ export class TableRepository implements ITableRepository { .selectFrom("undb_table") .selectAll("undb_table") .$if(spec.isSome(), (qb) => new TableReferenceVisitor(qb).call(spec.unwrap())) - .where((eb) => new TableDbQuerySpecHandler(this.qb, eb, ignoreSpace).handle(spec)) + .where((eb) => + new TableDbQuerySpecHandler(this.qb, eb, this.context.mustGetCurrentSpaceId(), ignoreSpace).handle(spec), + ) const tbs = await query.execute() return tbs.map((t) => this.mapper.toDo(t)) @@ -138,7 +143,7 @@ export class TableRepository implements ITableRepository { .selectFrom("undb_table") .selectAll("undb_table") .$if(spec.isSome(), (qb) => new TableReferenceVisitor(qb).call(spec.unwrap())) - .where((eb) => new TableDbQuerySpecHandler(this.qb, eb).handle(spec)) + .where((eb) => new TableDbQuerySpecHandler(this.qb, eb, this.context.mustGetCurrentSpaceId()).handle(spec)) .executeTakeFirst() if (!tb) { @@ -154,7 +159,7 @@ export class TableRepository implements ITableRepository { .selectFrom("undb_table") .selectAll("undb_table") .$call((qb) => new TableReferenceVisitor(qb).call(spec.unwrap())) - .where((eb) => new TableDbQuerySpecHandler(this.qb, eb).handle(spec)) + .where((eb) => new TableDbQuerySpecHandler(this.qb, eb, this.context.mustGetCurrentSpaceId()).handle(spec)) .executeTakeFirst() return tb ? Some(this.mapper.toDo(tb)) : None @@ -166,7 +171,7 @@ export class TableRepository implements ITableRepository { .selectFrom("undb_table") .selectAll("undb_table") .$call((qb) => new TableReferenceVisitor(qb).call(spec.unwrap())) - .where((eb) => new TableDbQuerySpecHandler(this.qb, eb).handle(spec)) + .where((eb) => new TableDbQuerySpecHandler(this.qb, eb, this.context.mustGetCurrentSpaceId()).handle(spec)) .execute() return tbs.map((t) => this.mapper.toDo(t)) diff --git a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts index 30405d486..6e4f6a792 100644 --- a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts @@ -1,4 +1,4 @@ -import { getCurrentUserId } from "@undb/context/server" +import type { IContext } from "@undb/context" import { WontImplementException, type ISpecification, type ISpecVisitor } from "@undb/domain" import { CREATED_AT_TYPE, @@ -59,6 +59,7 @@ export class UnderlyingTableSpecVisitor implements ITableSpecVisitor { constructor( public readonly table: UnderlyingTable, public readonly qb: IRecordQueryBuilder, + public readonly context: IContext, ) { this.tb = qb.schema.alterTable(table.name) } @@ -162,7 +163,7 @@ export class UnderlyingTableSpecVisitor implements ITableSpecVisitor { this.addSql(duplicateDataSql) - const userId = getCurrentUserId() + const userId = this.context.getCurrentUserId() const updateSql = this.qb .updateTable(duplicatedTable.id.value) .set((eb) => ({ diff --git a/packages/persistence/src/underlying/underlying-table.service.ts b/packages/persistence/src/underlying/underlying-table.service.ts index 825812898..2245fd36c 100644 --- a/packages/persistence/src/underlying/underlying-table.service.ts +++ b/packages/persistence/src/underlying/underlying-table.service.ts @@ -1,8 +1,9 @@ +import { injectContext,type IContext } from "@undb/context" import { singleton } from "@undb/di" import { createLogger } from "@undb/logger" -import type { TableComositeSpecification, TableDo } from "@undb/table" +import type { TableComositeSpecification,TableDo } from "@undb/table" import type { CompiledQuery } from "kysely" -import { getAnonymousTransaction, getCurrentTransaction } from "../ctx" +import { getAnonymousTransaction,getCurrentTransaction } from "../ctx" import { JoinTable } from "./reference/join-table" import { UnderlyingTable } from "./underlying-table" import { UnderlyingTableFieldVisitor } from "./underlying-table-field.visitor" @@ -10,6 +11,8 @@ import { UnderlyingTableSpecVisitor } from "./underlying-table-spec.visitor" @singleton() export class UnderlyingTableService { + constructor(@injectContext() private readonly context: IContext) {} + readonly logger = createLogger(UnderlyingTableService.name) async create(table: TableDo) { @@ -37,7 +40,7 @@ export class UnderlyingTableService { const t = new UnderlyingTable(table) const trx = getAnonymousTransaction() - const visitor = new UnderlyingTableSpecVisitor(t, trx) + const visitor = new UnderlyingTableSpecVisitor(t, trx, this.context) spec.accept(visitor) await visitor.execute() diff --git a/packages/persistence/src/webhook/webhook.filter-visitor.ts b/packages/persistence/src/webhook/webhook.filter-visitor.ts index 79a168e95..dfdb4f7b1 100644 --- a/packages/persistence/src/webhook/webhook.filter-visitor.ts +++ b/packages/persistence/src/webhook/webhook.filter-visitor.ts @@ -1,4 +1,3 @@ -import { mustGetCurrentSpaceId } from "@undb/context/server" import { WontImplementException } from "@undb/domain" import type { IWebhookSpecVisitor, @@ -21,11 +20,11 @@ import type { Database } from "../db" export class WebhookFilterVisitor extends AbstractQBVisitor implements IWebhookSpecVisitor { constructor( protected readonly eb: ExpressionBuilder, + private readonly spaceId: string, cloned = false, ) { super(eb) if (!cloned) { - const spaceId = mustGetCurrentSpaceId() this.addCond(this.eb.eb("space_id", "=", spaceId)) } } @@ -68,6 +67,6 @@ export class WebhookFilterVisitor extends AbstractQBVisitor implement this.addCond(cond) } clone(): this { - return new WebhookFilterVisitor(this.eb, true) as this + return new WebhookFilterVisitor(this.eb, this.spaceId, true) as this } } diff --git a/packages/persistence/src/webhook/webhook.mapper.ts b/packages/persistence/src/webhook/webhook.mapper.ts index 7363cb288..0262fbf03 100644 --- a/packages/persistence/src/webhook/webhook.mapper.ts +++ b/packages/persistence/src/webhook/webhook.mapper.ts @@ -1,4 +1,4 @@ -import { mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" import { singleton } from "@undb/di" import type { Mapper } from "@undb/domain" import { WebhookDo, WebhookFactory, type IWebhookDTO } from "@undb/webhook" @@ -7,6 +7,10 @@ import { json } from "../qb" @singleton() export class WebhookMapper implements Mapper { + constructor( + @injectContext() + private readonly context: IContext, + ) {} toDo(entity: Webhook): WebhookDo { return WebhookFactory.fromJSON({ id: entity.id, @@ -45,7 +49,7 @@ export class WebhookMapper implements Mapper { method: data.method, condition: data.condition ? json(data.condition) : null, event: data.event, - space_id: mustGetCurrentSpaceId(), + space_id: this.context.mustGetCurrentSpaceId(), } } } diff --git a/packages/persistence/src/webhook/webhook.query-repository.ts b/packages/persistence/src/webhook/webhook.query-repository.ts index 30da715b3..06f957152 100644 --- a/packages/persistence/src/webhook/webhook.query-repository.ts +++ b/packages/persistence/src/webhook/webhook.query-repository.ts @@ -1,3 +1,4 @@ +import { injectContext, type IContext } from "@undb/context" import { inject, singleton } from "@undb/di" import { None, Some, type IPagination, type Option } from "@undb/domain" import type { IWebhookDTO, IWebhookQueryRepository, WebhookSpecification } from "@undb/webhook" @@ -13,6 +14,8 @@ export class WebhookQueryRepository implements IWebhookQueryRepository { private readonly mapper: WebhookMapper, @injectQueryBuilder() private readonly qb: IQueryBuilder, + @injectContext() + private readonly context: IContext, ) {} async findOneById(id: string): Promise> { @@ -30,7 +33,7 @@ export class WebhookQueryRepository implements IWebhookQueryRepository { .selectFrom("undb_webhook") .selectAll() .where((eb) => { - const visitor = new WebhookFilterVisitor(eb) + const visitor = new WebhookFilterVisitor(eb, this.context.mustGetCurrentSpaceId()) if (spec.isSome()) { spec.unwrap().accept(visitor) } diff --git a/packages/persistence/src/webhook/webhook.repository.ts b/packages/persistence/src/webhook/webhook.repository.ts index 76b53c6d0..f1d73b69a 100644 --- a/packages/persistence/src/webhook/webhook.repository.ts +++ b/packages/persistence/src/webhook/webhook.repository.ts @@ -1,3 +1,4 @@ +import { injectContext, type IContext } from "@undb/context" import { inject, singleton } from "@undb/di" import { None, Some, type Option } from "@undb/domain" import { type IWebhookRepository, type WebhookDo, type WebhookSpecification } from "@undb/webhook" @@ -15,6 +16,8 @@ export class WebhookRepository implements IWebhookRepository { private readonly mapper: WebhookMapper, @injectQueryBuilder() private readonly qb: IQueryBuilder, + @injectContext() + private readonly context: IContext, ) {} async findOneById(id: string): Promise> { @@ -36,7 +39,7 @@ export class WebhookRepository implements IWebhookRepository { .selectFrom("undb_webhook") .selectAll() .where((eb) => { - const visitor = new WebhookFilterVisitor(eb) + const visitor = new WebhookFilterVisitor(eb, this.context.mustGetCurrentSpaceId()) spec.accept(visitor) return visitor.cond }) diff --git a/packages/query-handlers/src/handlers/get-api-tokens.query-handler.ts b/packages/query-handlers/src/handlers/get-api-tokens.query-handler.ts index 404cede02..985fc8534 100644 --- a/packages/query-handlers/src/handlers/get-api-tokens.query-handler.ts +++ b/packages/query-handlers/src/handlers/get-api-tokens.query-handler.ts @@ -1,4 +1,4 @@ -import { mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" import { queryHandler } from "@undb/cqrs" import { singleton } from "@undb/di" import { type IQueryHandler } from "@undb/domain" @@ -17,10 +17,13 @@ export class GetApiTokensQueryHandler implements IQueryHandler { - const spec = new WithApiTokenUserId(query.userId).and(new WithApiTokenSpaceId(mustGetCurrentSpaceId())) + const spaceId = this.context.mustGetCurrentSpaceId() + const spec = new WithApiTokenUserId(query.userId).and(new WithApiTokenSpaceId(spaceId)) const tokens = await this.repo.find(spec) return tokens } diff --git a/packages/query-handlers/src/handlers/get-base-by-share.query-handler.ts b/packages/query-handlers/src/handlers/get-base-by-share.query-handler.ts index eba257e6d..9ec6552a8 100644 --- a/packages/query-handlers/src/handlers/get-base-by-share.query-handler.ts +++ b/packages/query-handlers/src/handlers/get-base-by-share.query-handler.ts @@ -1,6 +1,7 @@ import { injectSpaceMemberService, type ISpaceMemberService } from "@undb/authz" import { type IBaseDTO } from "@undb/base" -import { getCurrentUserId, setContextValue } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" +import { setContextValue } from "@undb/context/server" import { queryHandler } from "@undb/cqrs" import { singleton } from "@undb/di" import type { IQueryHandler } from "@undb/domain" @@ -18,10 +19,12 @@ export class GetBaseByShareQueryHandler implements IQueryHandler private readonly spaceSvc: ISpaceService, @injectSpaceMemberService() private readonly spaceMemberSvc: ISpaceMemberService, + @injectContext() + private readonly context: IContext, ) {} async execute(query: IGetBaseByShareQuery): Promise { - const userId = getCurrentUserId() + const userId = this.context.mustGetCurrentUserId() const space = await this.spaceSvc.setSpaceContext(setContextValue, { shareId: query.shareId }) await this.spaceMemberSvc.setSpaceMemberContext(setContextValue, space.id.value, userId) diff --git a/packages/query-handlers/src/handlers/get-bases.query-handler.ts b/packages/query-handlers/src/handlers/get-bases.query-handler.ts index 528679c74..c1924dedf 100644 --- a/packages/query-handlers/src/handlers/get-bases.query-handler.ts +++ b/packages/query-handlers/src/handlers/get-bases.query-handler.ts @@ -1,5 +1,5 @@ import { injectBaseQueryRepository, WithBaseSpaceId, type IBaseDTO, type IBaseQueryRepository } from "@undb/base" -import { mustGetCurrentSpaceId } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" import { queryHandler } from "@undb/cqrs" import { singleton } from "@undb/di" import { Some, type IQueryHandler } from "@undb/domain" @@ -11,10 +11,13 @@ export class GetBasesQueryHandler implements IQueryHandler { constructor( @injectBaseQueryRepository() private readonly repo: IBaseQueryRepository, + @injectContext() + private readonly context: IContext, ) {} async execute(query: GetBasesQuery): Promise { - const spec = new WithBaseSpaceId(mustGetCurrentSpaceId()) + const spaceId = this.context.mustGetCurrentSpaceId() + const spec = new WithBaseSpaceId(spaceId) return this.repo.find(Some(spec)) } } diff --git a/packages/query-handlers/src/handlers/get-share.query-handler.ts b/packages/query-handlers/src/handlers/get-share.query-handler.ts index bd00d3248..b28bc8897 100644 --- a/packages/query-handlers/src/handlers/get-share.query-handler.ts +++ b/packages/query-handlers/src/handlers/get-share.query-handler.ts @@ -1,5 +1,6 @@ import { injectSpaceMemberService, type ISpaceMemberService } from "@undb/authz" -import { getCurrentUserId, setContextValue } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" +import { setContextValue } from "@undb/context/server" import { queryHandler } from "@undb/cqrs" import { singleton } from "@undb/di" import type { IQueryHandler } from "@undb/domain" @@ -17,10 +18,12 @@ export class GetShareQueryHandler implements IQueryHandler { private readonly spaceSvc: ISpaceService, @injectSpaceMemberService() private readonly spaceMemberSvc: ISpaceMemberService, + @injectContext() + private readonly context: IContext, ) {} async execute({ shareId }: IGetShareQuery): Promise { - const userId = getCurrentUserId() + const userId = this.context.mustGetCurrentUserId() const space = await this.spaceSvc.setSpaceContext(setContextValue, { shareId }) await this.spaceMemberSvc.setSpaceMemberContext(setContextValue, space.id.value, userId) diff --git a/packages/query-handlers/src/handlers/get-table-by-share-table.query-handler.ts b/packages/query-handlers/src/handlers/get-table-by-share-table.query-handler.ts index 92f9525a9..da0e6bf95 100644 --- a/packages/query-handlers/src/handlers/get-table-by-share-table.query-handler.ts +++ b/packages/query-handlers/src/handlers/get-table-by-share-table.query-handler.ts @@ -1,5 +1,6 @@ import { injectSpaceMemberService, type ISpaceMemberService } from "@undb/authz" -import { getCurrentUserId, setContextValue } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" +import { setContextValue } from "@undb/context/server" import { queryHandler } from "@undb/cqrs" import { singleton } from "@undb/di" import type { IQueryHandler } from "@undb/domain" @@ -18,10 +19,12 @@ export class GetTableByShareBaseQueryHandler implements IQueryHandler { - const userId = getCurrentUserId() + const userId = this.context.mustGetCurrentUserId() const space = await this.spaceSvc.setSpaceContext(setContextValue, { shareId: query.shareId }) await this.spaceMemberSvc.setSpaceMemberContext(setContextValue, space.id.value, userId) diff --git a/packages/query-handlers/src/handlers/get-table-by-share.query-handler.ts b/packages/query-handlers/src/handlers/get-table-by-share.query-handler.ts index 71d1dfbee..db9f9b617 100644 --- a/packages/query-handlers/src/handlers/get-table-by-share.query-handler.ts +++ b/packages/query-handlers/src/handlers/get-table-by-share.query-handler.ts @@ -1,5 +1,6 @@ import { injectSpaceMemberService, type ISpaceMemberService } from "@undb/authz" -import { getCurrentUserId, setContextValue } from "@undb/context/server" +import { injectContext, type IContext } from "@undb/context" +import { setContextValue } from "@undb/context/server" import { queryHandler } from "@undb/cqrs" import { singleton } from "@undb/di" import type { IQueryHandler } from "@undb/domain" @@ -18,10 +19,12 @@ export class GetTableByShareQueryHandler implements IQueryHandler { - const userId = getCurrentUserId() + const userId = this.context.mustGetCurrentUserId() const space = await this.spaceSvc.setSpaceContext(setContextValue, { shareId: query.shareId }) await this.spaceMemberSvc.setSpaceMemberContext(setContextValue, space.id.value, userId) From 21b993e9b9ca5720b6da863522e1d05bf767ea02 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Thu, 10 Oct 2024 14:46:27 +0800 Subject: [PATCH 3/5] feat: init chart --- .../drizzle/0007_steep_dragon_lord.sql | 1 + apps/backend/drizzle/meta/0007_snapshot.json | 1796 +++++++++++++++++ apps/backend/drizzle/meta/_journal.json | 7 + apps/frontend/schema.graphql | 7 + .../lib/components/blocks/chart/chart.svelte | 13 + .../blocks/chart/count-chart.svelte | 10 + .../blocks/grid-view/grid-view.svelte | 4 + .../blocks/table-tools/table-tools.svelte | 2 + .../view-widget/view-widget-button.svelte | 10 + .../view-widget/view-widget-sheet.svelte | 62 + .../blocks/widget/create-widget-form.svelte | 76 + .../components/blocks/widget/widget.svelte | 12 + apps/frontend/src/lib/store/modal.store.ts | 2 + .../(authed)/(space)/t/[tableId]/+layout.gql | 6 + .../create-view-widget.command-handler.ts | 22 + .../command-handlers/src/handlers/index.ts | 2 + .../src/create-view-widget.command.ts | 22 + packages/commands/src/index.ts | 1 + packages/graphql/src/index.ts | 7 + .../src/table/table.filter-visitor.ts | 4 + .../src/table/table.mutation-visitor.ts | 4 + .../src/table/table.reference-visitor.ts | 2 + .../persistence/src/table/table.repository.ts | 12 +- packages/persistence/src/tables.ts | 1 + .../underlying-table-spec.visitor.ts | 2 + packages/table/src/dto/create-table.dto.ts | 5 +- packages/table/src/modules/chart/chart.vo.ts | 41 + packages/table/src/modules/index.ts | 1 + .../src/modules/views/dto/create-view.dto.ts | 2 + .../table/src/modules/views/view/index.ts | 1 + .../views/view/variants/abstract-view.vo.ts | 21 +- .../modules/views/view/view-widget/index.ts | 1 + .../views/view/view-widget/view-widget.dto.ts | 10 + packages/table/src/modules/views/views.vo.ts | 12 + packages/table/src/modules/widgets/index.ts | 1 + .../table/src/modules/widgets/widget-id.vo.ts | 12 + .../src/modules/widgets/widget-name.vo.ts | 12 + .../table/src/modules/widgets/widget.vo.ts | 61 + .../table-view.specification.ts | 23 + .../specifications/table-visitor.interface.ts | 2 + packages/trpc/src/router.ts | 16 +- 41 files changed, 2296 insertions(+), 12 deletions(-) create mode 100644 apps/backend/drizzle/0007_steep_dragon_lord.sql create mode 100644 apps/backend/drizzle/meta/0007_snapshot.json create mode 100644 apps/frontend/src/lib/components/blocks/chart/chart.svelte create mode 100644 apps/frontend/src/lib/components/blocks/chart/count-chart.svelte create mode 100644 apps/frontend/src/lib/components/blocks/view-widget/view-widget-button.svelte create mode 100644 apps/frontend/src/lib/components/blocks/view-widget/view-widget-sheet.svelte create mode 100644 apps/frontend/src/lib/components/blocks/widget/create-widget-form.svelte create mode 100644 apps/frontend/src/lib/components/blocks/widget/widget.svelte create mode 100644 packages/command-handlers/src/handlers/create-view-widget.command-handler.ts create mode 100644 packages/commands/src/create-view-widget.command.ts create mode 100644 packages/table/src/modules/chart/chart.vo.ts create mode 100644 packages/table/src/modules/views/view/view-widget/index.ts create mode 100644 packages/table/src/modules/views/view/view-widget/view-widget.dto.ts create mode 100644 packages/table/src/modules/widgets/index.ts create mode 100644 packages/table/src/modules/widgets/widget-id.vo.ts create mode 100644 packages/table/src/modules/widgets/widget-name.vo.ts create mode 100644 packages/table/src/modules/widgets/widget.vo.ts diff --git a/apps/backend/drizzle/0007_steep_dragon_lord.sql b/apps/backend/drizzle/0007_steep_dragon_lord.sql new file mode 100644 index 000000000..49153cf17 --- /dev/null +++ b/apps/backend/drizzle/0007_steep_dragon_lord.sql @@ -0,0 +1 @@ +ALTER TABLE `undb_table` ADD `widgets` text; \ No newline at end of file diff --git a/apps/backend/drizzle/meta/0007_snapshot.json b/apps/backend/drizzle/meta/0007_snapshot.json new file mode 100644 index 000000000..cb0d54af8 --- /dev/null +++ b/apps/backend/drizzle/meta/0007_snapshot.json @@ -0,0 +1,1796 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "7f4f5874-e1a9-431c-a1e8-926866197d71", + "prevId": "f4f5f7dd-cbf3-45de-a072-b086f2e2c170", + "tables": { + "undb_api_token": { + "name": "undb_api_token", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "undb_api_token_token_unique": { + "name": "undb_api_token_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "api_token_space_id_idx": { + "name": "api_token_space_id_idx", + "columns": [ + "space_id" + ], + "isUnique": false + }, + "api_token_user_id_idx": { + "name": "api_token_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "undb_api_token_user_id_undb_user_id_fk": { + "name": "undb_api_token_user_id_undb_user_id_fk", + "tableFrom": "undb_api_token", + "tableTo": "undb_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_api_token_space_id_undb_space_id_fk": { + "name": "undb_api_token_space_id_undb_space_id_fk", + "tableFrom": "undb_api_token", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_attachment_mapping": { + "name": "undb_attachment_mapping", + "columns": { + "attachment_id": { + "name": "attachment_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "record_id": { + "name": "record_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "field_id": { + "name": "field_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "undb_attachment_mapping_attachment_id_undb_attachment_id_fk": { + "name": "undb_attachment_mapping_attachment_id_undb_attachment_id_fk", + "tableFrom": "undb_attachment_mapping", + "tableTo": "undb_attachment", + "columnsFrom": [ + "attachment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_attachment_mapping_table_id_undb_table_id_fk": { + "name": "undb_attachment_mapping_table_id_undb_table_id_fk", + "tableFrom": "undb_attachment_mapping", + "tableTo": "undb_table", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "undb_attachment_mapping_attachment_id_table_id_record_id_field_id_pk": { + "columns": [ + "attachment_id", + "table_id", + "record_id", + "field_id" + ], + "name": "undb_attachment_mapping_attachment_id_table_id_record_id_field_id_pk" + } + }, + "uniqueConstraints": {} + }, + "undb_attachment": { + "name": "undb_attachment", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "attachment_size_idx": { + "name": "attachment_size_idx", + "columns": [ + "size" + ], + "isUnique": false + }, + "attachment_space_id_idx": { + "name": "attachment_space_id_idx", + "columns": [ + "space_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "undb_attachment_created_by_undb_user_id_fk": { + "name": "undb_attachment_created_by_undb_user_id_fk", + "tableFrom": "undb_attachment", + "tableTo": "undb_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_attachment_space_id_undb_space_id_fk": { + "name": "undb_attachment_space_id_undb_space_id_fk", + "tableFrom": "undb_attachment", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_audit": { + "name": "undb_audit", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "meta": { + "name": "meta", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "op": { + "name": "op", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "record_id": { + "name": "record_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "operator_id": { + "name": "operator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "audit_table_id_idx": { + "name": "audit_table_id_idx", + "columns": [ + "table_id" + ], + "isUnique": false + }, + "audit_space_id_idx": { + "name": "audit_space_id_idx", + "columns": [ + "space_id" + ], + "isUnique": false + }, + "audit_record_id_idx": { + "name": "audit_record_id_idx", + "columns": [ + "record_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "undb_audit_space_id_undb_space_id_fk": { + "name": "undb_audit_space_id_undb_space_id_fk", + "tableFrom": "undb_audit", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_base": { + "name": "undb_base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "base_space_id_idx": { + "name": "base_space_id_idx", + "columns": [ + "space_id" + ], + "isUnique": false + }, + "base_name_unique_idx": { + "name": "base_name_unique_idx", + "columns": [ + "name", + "space_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "undb_base_space_id_undb_space_id_fk": { + "name": "undb_base_space_id_undb_space_id_fk", + "tableFrom": "undb_base", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_base_created_by_undb_user_id_fk": { + "name": "undb_base_created_by_undb_user_id_fk", + "tableFrom": "undb_base", + "tableTo": "undb_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_base_updated_by_undb_user_id_fk": { + "name": "undb_base_updated_by_undb_user_id_fk", + "tableFrom": "undb_base", + "tableTo": "undb_user", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_email_verification_code": { + "name": "undb_email_verification_code", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "undb_email_verification_code_user_id_unique": { + "name": "undb_email_verification_code_user_id_unique", + "columns": [ + "user_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "undb_email_verification_code_user_id_undb_user_id_fk": { + "name": "undb_email_verification_code_user_id_undb_user_id_fk", + "tableFrom": "undb_email_verification_code", + "tableTo": "undb_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_invitation": { + "name": "undb_invitation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "invited_at": { + "name": "invited_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "invitation_space_id_idx": { + "name": "invitation_space_id_idx", + "columns": [ + "space_id" + ], + "isUnique": false + }, + "invitation_unique_idx": { + "name": "invitation_unique_idx", + "columns": [ + "email", + "space_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "undb_invitation_space_id_undb_space_id_fk": { + "name": "undb_invitation_space_id_undb_space_id_fk", + "tableFrom": "undb_invitation", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_invitation_inviter_id_undb_user_id_fk": { + "name": "undb_invitation_inviter_id_undb_user_id_fk", + "tableFrom": "undb_invitation", + "tableTo": "undb_user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_oauth_account": { + "name": "undb_oauth_account", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "undb_oauth_account_user_id_undb_user_id_fk": { + "name": "undb_oauth_account_user_id_undb_user_id_fk", + "tableFrom": "undb_oauth_account", + "tableTo": "undb_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "undb_oauth_account_provider_id_provider_user_id_pk": { + "columns": [ + "provider_id", + "provider_user_id" + ], + "name": "undb_oauth_account_provider_id_provider_user_id_pk" + } + }, + "uniqueConstraints": {} + }, + "undb_outbox": { + "name": "undb_outbox", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "meta": { + "name": "meta", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "outbox_space_id_idx": { + "name": "outbox_space_id_idx", + "columns": [ + "space_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "undb_outbox_space_id_undb_space_id_fk": { + "name": "undb_outbox_space_id_undb_space_id_fk", + "tableFrom": "undb_outbox", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_password_reset_token": { + "name": "undb_password_reset_token", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "undb_password_reset_token_token_unique": { + "name": "undb_password_reset_token_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "password_reset_token_user_id_idx": { + "name": "password_reset_token_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "undb_password_reset_token_user_id_undb_user_id_fk": { + "name": "undb_password_reset_token_user_id_undb_user_id_fk", + "tableFrom": "undb_password_reset_token", + "tableTo": "undb_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_reference_id_mapping": { + "name": "undb_reference_id_mapping", + "columns": { + "field_id": { + "name": "field_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "symmetric_field_id": { + "name": "symmetric_field_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "foreign_table_id": { + "name": "foreign_table_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "reference_id_mapping_unique_idx": { + "name": "reference_id_mapping_unique_idx", + "columns": [ + "field_id", + "table_id", + "symmetric_field_id", + "foreign_table_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "undb_reference_id_mapping_table_id_undb_table_id_fk": { + "name": "undb_reference_id_mapping_table_id_undb_table_id_fk", + "tableFrom": "undb_reference_id_mapping", + "tableTo": "undb_table", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_reference_id_mapping_foreign_table_id_undb_table_id_fk": { + "name": "undb_reference_id_mapping_foreign_table_id_undb_table_id_fk", + "tableFrom": "undb_reference_id_mapping", + "tableTo": "undb_table", + "columnsFrom": [ + "foreign_table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_rollup_id_mapping": { + "name": "undb_rollup_id_mapping", + "columns": { + "field_id": { + "name": "field_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rollup_id": { + "name": "rollup_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rollup_table_id": { + "name": "rollup_table_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "undb_rollup_id_mapping_table_id_undb_table_id_fk": { + "name": "undb_rollup_id_mapping_table_id_undb_table_id_fk", + "tableFrom": "undb_rollup_id_mapping", + "tableTo": "undb_table", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_rollup_id_mapping_rollup_table_id_undb_table_id_fk": { + "name": "undb_rollup_id_mapping_rollup_table_id_undb_table_id_fk", + "tableFrom": "undb_rollup_id_mapping", + "tableTo": "undb_table", + "columnsFrom": [ + "rollup_table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "undb_rollup_id_mapping_field_id_rollup_id_pk": { + "columns": [ + "field_id", + "rollup_id" + ], + "name": "undb_rollup_id_mapping_field_id_rollup_id_pk" + } + }, + "uniqueConstraints": {} + }, + "undb_session": { + "name": "undb_session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "undb_session_user_id_undb_user_id_fk": { + "name": "undb_session_user_id_undb_user_id_fk", + "tableFrom": "undb_session", + "tableTo": "undb_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_session_space_id_undb_space_id_fk": { + "name": "undb_session_space_id_undb_space_id_fk", + "tableFrom": "undb_session", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_share": { + "name": "undb_share", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "share_space_id_idx": { + "name": "share_space_id_idx", + "columns": [ + "space_id" + ], + "isUnique": false + }, + "share_unique_idx": { + "name": "share_unique_idx", + "columns": [ + "target_type", + "target_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "undb_share_space_id_undb_space_id_fk": { + "name": "undb_share_space_id_undb_space_id_fk", + "tableFrom": "undb_share", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_space": { + "name": "undb_space", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_personal": { + "name": "is_personal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "space_name_idx": { + "name": "space_name_idx", + "columns": [ + "name" + ], + "isUnique": false + } + }, + "foreignKeys": { + "undb_space_created_by_undb_user_id_fk": { + "name": "undb_space_created_by_undb_user_id_fk", + "tableFrom": "undb_space", + "tableTo": "undb_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_space_updated_by_undb_user_id_fk": { + "name": "undb_space_updated_by_undb_user_id_fk", + "tableFrom": "undb_space", + "tableTo": "undb_user", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_space_deleted_by_undb_user_id_fk": { + "name": "undb_space_deleted_by_undb_user_id_fk", + "tableFrom": "undb_space", + "tableTo": "undb_user", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_space_member": { + "name": "undb_space_member", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "space_member_unique_idx": { + "name": "space_member_unique_idx", + "columns": [ + "user_id", + "space_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "undb_space_member_user_id_undb_user_id_fk": { + "name": "undb_space_member_user_id_undb_user_id_fk", + "tableFrom": "undb_space_member", + "tableTo": "undb_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_space_member_space_id_undb_space_id_fk": { + "name": "undb_space_member_space_id_undb_space_id_fk", + "tableFrom": "undb_space_member", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_table_id_mapping": { + "name": "undb_table_id_mapping", + "columns": { + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "undb_table_id_mapping_table_id_undb_table_id_fk": { + "name": "undb_table_id_mapping_table_id_undb_table_id_fk", + "tableFrom": "undb_table_id_mapping", + "tableTo": "undb_table", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "undb_table_id_mapping_table_id_subject_id_pk": { + "columns": [ + "table_id", + "subject_id" + ], + "name": "undb_table_id_mapping_table_id_subject_id_pk" + } + }, + "uniqueConstraints": {} + }, + "undb_table": { + "name": "undb_table", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_id": { + "name": "base_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "views": { + "name": "views", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "forms": { + "name": "forms", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rls": { + "name": "rls", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "widgets": { + "name": "widgets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "table_base_id_idx": { + "name": "table_base_id_idx", + "columns": [ + "base_id" + ], + "isUnique": false + }, + "table_space_id_idx": { + "name": "table_space_id_idx", + "columns": [ + "space_id" + ], + "isUnique": false + }, + "table_name_unique_idx": { + "name": "table_name_unique_idx", + "columns": [ + "name", + "base_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "undb_table_base_id_undb_base_id_fk": { + "name": "undb_table_base_id_undb_base_id_fk", + "tableFrom": "undb_table", + "tableTo": "undb_base", + "columnsFrom": [ + "base_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_table_space_id_undb_space_id_fk": { + "name": "undb_table_space_id_undb_space_id_fk", + "tableFrom": "undb_table", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_table_created_by_undb_user_id_fk": { + "name": "undb_table_created_by_undb_user_id_fk", + "tableFrom": "undb_table", + "tableTo": "undb_user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_table_updated_by_undb_user_id_fk": { + "name": "undb_table_updated_by_undb_user_id_fk", + "tableFrom": "undb_table", + "tableTo": "undb_user", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_user": { + "name": "undb_user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "undb_user_email_unique": { + "name": "undb_user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "user_username_idx": { + "name": "user_username_idx", + "columns": [ + "username" + ], + "isUnique": false + }, + "user_email_idx": { + "name": "user_email_idx", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "undb_webhook": { + "name": "undb_webhook", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "headers": { + "name": "headers", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "condition": { + "name": "condition", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "space_id": { + "name": "space_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "webhook_table_id_idx": { + "name": "webhook_table_id_idx", + "columns": [ + "table_id" + ], + "isUnique": false + }, + "webhook_space_id_idx": { + "name": "webhook_space_id_idx", + "columns": [ + "space_id" + ], + "isUnique": false + }, + "webhook_url_idx": { + "name": "webhook_url_idx", + "columns": [ + "url" + ], + "isUnique": false + } + }, + "foreignKeys": { + "undb_webhook_table_id_undb_table_id_fk": { + "name": "undb_webhook_table_id_undb_table_id_fk", + "tableFrom": "undb_webhook", + "tableTo": "undb_table", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "undb_webhook_space_id_undb_space_id_fk": { + "name": "undb_webhook_space_id_undb_space_id_fk", + "tableFrom": "undb_webhook", + "tableTo": "undb_space", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/apps/backend/drizzle/meta/_journal.json b/apps/backend/drizzle/meta/_journal.json index 8b955642d..f480c5c3b 100644 --- a/apps/backend/drizzle/meta/_journal.json +++ b/apps/backend/drizzle/meta/_journal.json @@ -50,6 +50,13 @@ "when": 1728358607342, "tag": "0006_mature_madame_web", "breakpoints": true + }, + { + "idx": 7, + "version": "6", + "when": 1728539365470, + "tag": "0007_steep_dragon_lord", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/frontend/schema.graphql b/apps/frontend/schema.graphql index f9d483a31..9a1a83079 100644 --- a/apps/frontend/schema.graphql +++ b/apps/frontend/schema.graphql @@ -254,6 +254,7 @@ type View { shareId: ID sort: JSON type: ViewType! + widgets: [Widget] } type ViewData { @@ -269,3 +270,9 @@ enum ViewType { grid kanban } + +type Widget { + id: ID! + item: JSON + name: String! +} diff --git a/apps/frontend/src/lib/components/blocks/chart/chart.svelte b/apps/frontend/src/lib/components/blocks/chart/chart.svelte new file mode 100644 index 000000000..26b199835 --- /dev/null +++ b/apps/frontend/src/lib/components/blocks/chart/chart.svelte @@ -0,0 +1,13 @@ + + + diff --git a/apps/frontend/src/lib/components/blocks/chart/count-chart.svelte b/apps/frontend/src/lib/components/blocks/chart/count-chart.svelte new file mode 100644 index 000000000..8f6ae83f6 --- /dev/null +++ b/apps/frontend/src/lib/components/blocks/chart/count-chart.svelte @@ -0,0 +1,10 @@ + + + +
+ 50 +
+
diff --git a/apps/frontend/src/lib/components/blocks/grid-view/grid-view.svelte b/apps/frontend/src/lib/components/blocks/grid-view/grid-view.svelte index 0facbe0f2..27493e145 100644 --- a/apps/frontend/src/lib/components/blocks/grid-view/grid-view.svelte +++ b/apps/frontend/src/lib/components/blocks/grid-view/grid-view.svelte @@ -80,3 +80,7 @@ {#await import("$lib/components/blocks/record-detail/table-record-detail-sheet.svelte") then { default: TableRecordDetailSheet }} {/await} + +{#await import("$lib/components/blocks/view-widget/view-widget-sheet.svelte") then { default: ViewWidgetSheet }} + +{/await} diff --git a/apps/frontend/src/lib/components/blocks/table-tools/table-tools.svelte b/apps/frontend/src/lib/components/blocks/table-tools/table-tools.svelte index 40ca27e79..8b7970d14 100644 --- a/apps/frontend/src/lib/components/blocks/table-tools/table-tools.svelte +++ b/apps/frontend/src/lib/components/blocks/table-tools/table-tools.svelte @@ -8,6 +8,7 @@ import ViewFields from "../view-fields/view-fields.svelte" import ShareViewButton from "../share/share-view-button.svelte" import BulkUpdateRecordsButton from "../bulk-update-records/bulk-update-records-button.svelte" + import ViewWidgetButton from "../view-widget/view-widget-button.svelte" import type { Writable } from "svelte/store" export let readonly = false @@ -33,5 +34,6 @@ {/if} +
diff --git a/apps/frontend/src/lib/components/blocks/view-widget/view-widget-button.svelte b/apps/frontend/src/lib/components/blocks/view-widget/view-widget-button.svelte new file mode 100644 index 000000000..ea3509ff8 --- /dev/null +++ b/apps/frontend/src/lib/components/blocks/view-widget/view-widget-button.svelte @@ -0,0 +1,10 @@ + + + diff --git a/apps/frontend/src/lib/components/blocks/view-widget/view-widget-sheet.svelte b/apps/frontend/src/lib/components/blocks/view-widget/view-widget-sheet.svelte new file mode 100644 index 000000000..360ecb502 --- /dev/null +++ b/apps/frontend/src/lib/components/blocks/view-widget/view-widget-sheet.svelte @@ -0,0 +1,62 @@ + + + toggleModal(VIEW_WIDGET_MODAL)}> + + + Widgets + View Widgets + +
+ {#each $widgets as widget} + + {/each} + + + + + + {#if $viewId} + { + open = false + }} + /> + {/if} + + +
+ + + + + +
+
diff --git a/apps/frontend/src/lib/components/blocks/widget/create-widget-form.svelte b/apps/frontend/src/lib/components/blocks/widget/create-widget-form.svelte new file mode 100644 index 000000000..fa5bc5a27 --- /dev/null +++ b/apps/frontend/src/lib/components/blocks/widget/create-widget-form.svelte @@ -0,0 +1,76 @@ + + + + + + Name + + + + + + + diff --git a/apps/frontend/src/lib/components/blocks/widget/widget.svelte b/apps/frontend/src/lib/components/blocks/widget/widget.svelte new file mode 100644 index 000000000..b7e86a3f0 --- /dev/null +++ b/apps/frontend/src/lib/components/blocks/widget/widget.svelte @@ -0,0 +1,12 @@ + + +
+ {#if widget.item.type === "chart"} + + {/if} +
diff --git a/apps/frontend/src/lib/store/modal.store.ts b/apps/frontend/src/lib/store/modal.store.ts index 1309ea49b..0f49c6c51 100644 --- a/apps/frontend/src/lib/store/modal.store.ts +++ b/apps/frontend/src/lib/store/modal.store.ts @@ -22,6 +22,7 @@ export const DUPLICATE_BASE_MODAL = "duplicateBase" as const export const UPDATE_BASE_MODAL = "updateBase" as const export const DELETE_TABLE_MODAL = "deleteTable" as const export const IMPORT_TEMPLATE_MODAL = "importTemplate" as const +export const VIEW_WIDGET_MODAL = "viewWidget" as const type ModalType = | typeof CREATE_TABLE_MODAL @@ -42,6 +43,7 @@ type ModalType = | typeof DELETE_TABLE_MODAL | typeof DUPLICATE_BASE_MODAL | typeof IMPORT_TEMPLATE_MODAL + | typeof VIEW_WIDGET_MODAL export const toggleModal = (type: ModalType) => { modal.update(($modal) => { diff --git a/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.gql b/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.gql index 9be009854..7ce7cc4dc 100644 --- a/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.gql +++ b/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.gql @@ -43,6 +43,12 @@ query GetTableQuery($tableId: ID!, $viewId: ID) { enabled id } + + widgets { + id + name + item + } } forms { diff --git a/packages/command-handlers/src/handlers/create-view-widget.command-handler.ts b/packages/command-handlers/src/handlers/create-view-widget.command-handler.ts new file mode 100644 index 000000000..ab90474c0 --- /dev/null +++ b/packages/command-handlers/src/handlers/create-view-widget.command-handler.ts @@ -0,0 +1,22 @@ +import { CreateViewWidgetCommand } from "@undb/commands" +import { commandHandler } from "@undb/cqrs" +import { singleton } from "@undb/di" +import type { ICommandHandler } from "@undb/domain" +import { injectTableRepository, TableIdVo, type ITableRepository } from "@undb/table" + +@commandHandler(CreateViewWidgetCommand) +@singleton() +export class CreateViewWidgetCommandHandler implements ICommandHandler { + constructor( + @injectTableRepository() + private readonly repo: ITableRepository, + ) {} + + async execute(command: CreateViewWidgetCommand): Promise { + const table = (await this.repo.findOneById(new TableIdVo(command.tableId))).expect("table not found") + + const spec = table.views.$createWidget(table, command) + + await this.repo.updateOneById(table, spec) + } +} diff --git a/packages/command-handlers/src/handlers/index.ts b/packages/command-handlers/src/handlers/index.ts index 26f456df3..47d63489d 100644 --- a/packages/command-handlers/src/handlers/index.ts +++ b/packages/command-handlers/src/handlers/index.ts @@ -13,6 +13,7 @@ import { CreateTableFieldCommandHandler } from "./create-table-field.command-han import { CreateTableFormCommandHandler } from "./create-table-form.command-handler" import { CreateTableViewCommandHandler } from "./create-table-view.command-handler" import { CreateTableCommandHandler } from "./create-table.command-handler" +import { CreateViewWidgetCommandHandler } from "./create-view-widget.command-handler" import { CreateWebhookCommandHandler } from "./create-webhook.command-handler" import { DeleteBaseCommandHandler } from "./delete-base.command-handler" import { DeleteInvitationCommandHandler } from "./delete-invitation.command-handler" @@ -110,4 +111,5 @@ export const commandHandlers = [ DuplicateTableFormCommandHandler, CreateFromTemplateCommandHandler, SetDefaultViewCommandHandler, + CreateViewWidgetCommandHandler, ] diff --git a/packages/commands/src/create-view-widget.command.ts b/packages/commands/src/create-view-widget.command.ts new file mode 100644 index 000000000..4c1c0e26d --- /dev/null +++ b/packages/commands/src/create-view-widget.command.ts @@ -0,0 +1,22 @@ +import { Command, type CommandProps } from "@undb/domain" +import { createViewWidgetDTO, tableId, type IWidgetDTO } from "@undb/table" +import { z } from "@undb/zod" + +export const createViewWidgetCommand = createViewWidgetDTO.extend({ + tableId: tableId, +}) + +export type ICreateViewWidgetCommand = z.infer + +export class CreateViewWidgetCommand extends Command implements ICreateViewWidgetCommand { + public readonly tableId: string + public readonly viewId: string + public readonly widget: IWidgetDTO + + constructor(props: CommandProps) { + super(props) + this.tableId = props.tableId + this.viewId = props.viewId + this.widget = props.widget + } +} diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index 39232467d..42b1a3fc2 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -13,6 +13,7 @@ export * from "./create-table-field.command" export * from "./create-table-form.command" export * from "./create-table-view.command" export * from "./create-table.command" +export * from "./create-view-widget.command" export * from "./create-webhook.command" export * from "./delete-base.command" export * from "./delete-form.command" diff --git a/packages/graphql/src/index.ts b/packages/graphql/src/index.ts index d6e94d9d5..7a556d7a8 100644 --- a/packages/graphql/src/index.ts +++ b/packages/graphql/src/index.ts @@ -148,6 +148,12 @@ export class Graphql { field: String } + type Widget { + id: ID! + name: String! + item: JSON + } + type View { id: ID! name: String! @@ -163,6 +169,7 @@ export class Graphql { grid: GridOption kanban: KanbanOption gallery: GalleryOption + widgets: [Widget] shareId: ID share: Share diff --git a/packages/persistence/src/table/table.filter-visitor.ts b/packages/persistence/src/table/table.filter-visitor.ts index 238437a5f..743d92200 100644 --- a/packages/persistence/src/table/table.filter-visitor.ts +++ b/packages/persistence/src/table/table.filter-visitor.ts @@ -33,6 +33,7 @@ import type { WithViewIdSpecification, WithViewOption, WithViewSort, + WithViewWidgets, } from "@undb/table" import type { ExpressionBuilder } from "kysely" import { AbstractQBVisitor } from "../abstract-qb.visitor" @@ -133,6 +134,9 @@ export class TableFilterVisitor extends AbstractQBVisitor implements IT withViewFields(fields: WithViewFields): void { throw new Error("Method not implemented.") } + withViewWidgets(spec: WithViewWidgets): void { + throw new Error("Method not implemented.") + } withForms(views: TableFormsSpecification): void { throw new Error("Method not implemented.") } diff --git a/packages/persistence/src/table/table.mutation-visitor.ts b/packages/persistence/src/table/table.mutation-visitor.ts index acc42687b..cce57a4f7 100644 --- a/packages/persistence/src/table/table.mutation-visitor.ts +++ b/packages/persistence/src/table/table.mutation-visitor.ts @@ -30,6 +30,7 @@ import type { WithViewIdSpecification, WithViewOption, WithViewSort, + WithViewWidgets, WithoutFieldSpecification, WithoutFormSpecification, WithoutView, @@ -170,6 +171,9 @@ export class TableMutationVisitor extends AbstractQBMutationVisitor implements I this.addSql(sql) } + withViewWidgets(spec: WithViewWidgets): void { + this.setData(tables.views.name, json(this.table.views.toJSON())) + } withoutView(view: WithoutView): void { this.setData(tables.views.name, json(this.table.views.toJSON())) const deleteQuery = this.qb diff --git a/packages/persistence/src/table/table.reference-visitor.ts b/packages/persistence/src/table/table.reference-visitor.ts index e85789a27..0118fdb7b 100644 --- a/packages/persistence/src/table/table.reference-visitor.ts +++ b/packages/persistence/src/table/table.reference-visitor.ts @@ -34,6 +34,7 @@ import type { WithViewIdSpecification, WithViewOption, WithViewSort, + WithViewWidgets, } from "@undb/table" import type { SelectQueryBuilder } from "kysely" import type { Database, Table } from "../db" @@ -80,6 +81,7 @@ export class TableReferenceVisitor implements ITableSpecVisitor { withForm(views: WithFormSpecification): void {} withForeignRollupField(spec: WithForeignRollupFieldSpec): void {} withTableForeignTables(spec: WithTableForeignTablesSpec): void {} + withViewWidgets(spec: WithViewWidgets): void {} withTableUnqueName(spec: TableUniqueNameSpecification): void { this.sqb = this.sqb .innerJoin("undb_base", "undb_table.base_id", "undb_base.id") diff --git a/packages/persistence/src/table/table.repository.ts b/packages/persistence/src/table/table.repository.ts index 6d8a47518..d934a7814 100644 --- a/packages/persistence/src/table/table.repository.ts +++ b/packages/persistence/src/table/table.repository.ts @@ -1,7 +1,7 @@ -import { injectContext, type IContext } from "@undb/context" -import { executionContext, getCurrentSpaceId } from "@undb/context/server" -import { inject, singleton } from "@undb/di" -import { None, Option, Some } from "@undb/domain" +import { injectContext,type IContext } from "@undb/context" +import { executionContext,getCurrentSpaceId } from "@undb/context/server" +import { inject,singleton } from "@undb/di" +import { None,Option,Some } from "@undb/domain" import { TableComositeSpecification, TableIdSpecification, @@ -13,8 +13,8 @@ import { type TableId, } from "@undb/table" import { getCurrentTransaction } from "../ctx" -import type { InsertTable, InsertTableIdMapping } from "../db" -import { json, type IQueryBuilder } from "../qb" +import type { InsertTable,InsertTableIdMapping } from "../db" +import { json,type IQueryBuilder } from "../qb" import { injectQueryBuilder } from "../qb.provider" import { UnderlyingTableService } from "../underlying/underlying-table.service" import { TableDbQuerySpecHandler } from "./table-db.query-spec-handler" diff --git a/packages/persistence/src/tables.ts b/packages/persistence/src/tables.ts index 3c3cad5f3..867499eba 100644 --- a/packages/persistence/src/tables.ts +++ b/packages/persistence/src/tables.ts @@ -53,6 +53,7 @@ export const tables = sqliteTable( views: text("views", { mode: "json" }).notNull(), forms: text("forms", { mode: "json" }), rls: text("rls", { mode: "json" }), + widgets: text("widgets", { mode: "json" }), createdAt: text("created_at") .notNull() diff --git a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts index 6e4f6a792..6a677af1b 100644 --- a/packages/persistence/src/underlying/underlying-table-spec.visitor.ts +++ b/packages/persistence/src/underlying/underlying-table-spec.visitor.ts @@ -7,6 +7,7 @@ import { UPDATED_AT_TYPE, UPDATED_BY_TYPE, WithViewFieldWidth, + WithViewWidgets, type DuplicatedTableSpecification, type ITableSpecVisitor, type SelectField, @@ -200,6 +201,7 @@ export class UnderlyingTableSpecVisitor implements ITableSpecVisitor { } withViewFields(fields: WithViewFields): void {} withViewFieldWidth(spec: WithViewFieldWidth): void {} + withViewWidgets(spec: WithViewWidgets): void {} withForm(views: WithFormSpecification): void {} withForms(views: TableFormsSpecification): void {} withNewForm(views: WithNewFormSpecification): void {} diff --git a/packages/table/src/dto/create-table.dto.ts b/packages/table/src/dto/create-table.dto.ts index b81f2fc8c..f9e5b97ba 100644 --- a/packages/table/src/dto/create-table.dto.ts +++ b/packages/table/src/dto/create-table.dto.ts @@ -1,4 +1,4 @@ -import { baseIdSchema } from "@undb/base" +import { baseIdSchema, baseNameSchema } from "@undb/base" import { spaceIdSchema } from "@undb/space" import { z } from "@undb/zod" import { createFormDTO } from "../modules/forms/dto/create-form.dto" @@ -13,7 +13,8 @@ import { tableName } from "../table-name.vo" export const createTableDTO = z.object({ id: tableId.optional(), name: tableName, - baseId: baseIdSchema, + baseId: baseIdSchema.optional(), + baseName: baseNameSchema.optional(), spaceId: spaceIdSchema, schema: createSchemaDTO, diff --git a/packages/table/src/modules/chart/chart.vo.ts b/packages/table/src/modules/chart/chart.vo.ts new file mode 100644 index 000000000..6a9b17067 --- /dev/null +++ b/packages/table/src/modules/chart/chart.vo.ts @@ -0,0 +1,41 @@ +import { ValueObject } from "@undb/domain" +import { z } from "@undb/zod" +import { fieldId } from "../schema" +import { createConditionGroup } from "../schema/fields/condition/condition.type" + +const chartFilterOption = z.undefined() + +export const chartFilterGroup = createConditionGroup(chartFilterOption, chartFilterOption) + +const countChart = z.object({ + type: z.literal("count"), + config: z.object({ + condition: chartFilterGroup.optional(), + }), +}) + +const pieChart = z.object({ + type: z.literal("pie"), + config: z.object({ + fieldId: fieldId, + aggregateFieldId: fieldId.optional(), + aggregateFunction: z.enum(["count", "sum"]).optional(), + }), +}) + +const barChart = z.object({ + type: z.literal("bar"), + config: z.object({ + xFieldId: fieldId, + yFieldId: fieldId, + aggregateFunction: z.enum(["count", "sum", "average"]).optional(), + groupByFieldId: fieldId.optional(), + stacked: z.boolean().optional(), + }), +}) + +export const chart = z.discriminatedUnion("type", [countChart, pieChart, barChart]) + +export type IChart = z.infer + +export class ChartVO extends ValueObject {} diff --git a/packages/table/src/modules/index.ts b/packages/table/src/modules/index.ts index 30a0aa414..e2d9aaf4c 100644 --- a/packages/table/src/modules/index.ts +++ b/packages/table/src/modules/index.ts @@ -6,3 +6,4 @@ export * from "./schema" export * from "./schema/fields/condition" export * from "./storage" export * from "./views" +export * from "./widgets" diff --git a/packages/table/src/modules/views/dto/create-view.dto.ts b/packages/table/src/modules/views/dto/create-view.dto.ts index 9c4a33682..bd36cc9b6 100644 --- a/packages/table/src/modules/views/dto/create-view.dto.ts +++ b/packages/table/src/modules/views/dto/create-view.dto.ts @@ -1,5 +1,6 @@ import { z } from "@undb/zod" import { tableId } from "../../../table-id.vo" +import { widgetDTO } from "../../widgets/widget.vo" import { viewAggregate, viewColorGroup, viewFields, viewFilterGroup, viewOption, viewSort } from "../view" import { galleryOption } from "../view/variants/gallery-view.vo" import { kanbanOption } from "../view/variants/kanban-view.vo" @@ -18,6 +19,7 @@ export const createBaseViewDTO = z.object({ sort: viewSort.optional(), fields: viewFields.optional(), aggregate: viewAggregate.optional(), + widgets: widgetDTO.array().optional(), }) export const createGridViewDTO = createBaseViewDTO.extend({ diff --git a/packages/table/src/modules/views/view/index.ts b/packages/table/src/modules/views/view/index.ts index 5e40a548d..f60d8831b 100644 --- a/packages/table/src/modules/views/view/index.ts +++ b/packages/table/src/modules/views/view/index.ts @@ -8,4 +8,5 @@ export * from "./view-id.vo" export * from "./view-name.vo" export * from "./view-option.vo" export * from "./view-sort" +export * from "./view-widget" export * from "./view.type" diff --git a/packages/table/src/modules/views/view/variants/abstract-view.vo.ts b/packages/table/src/modules/views/view/variants/abstract-view.vo.ts index a185a5b55..a54ea7eea 100644 --- a/packages/table/src/modules/views/view/variants/abstract-view.vo.ts +++ b/packages/table/src/modules/views/view/variants/abstract-view.vo.ts @@ -1,7 +1,7 @@ import { None, Option, Some, and } from "@undb/domain" import { z } from "@undb/zod" import type { IDuplicateViewDTO, IUpdateViewDTO } from "../../../../dto" -import type { TableComositeSpecification } from "../../../../specifications" +import { type TableComositeSpecification } from "../../../../specifications" import { WithNewView, WithView, @@ -11,11 +11,13 @@ import { WithViewFilter, WithViewOption, WithViewSort, + WithViewWidgets, WithoutView, } from "../../../../specifications/table-view.specification" import { tableId } from "../../../../table-id.vo" import type { TableDo } from "../../../../table.do" import type { Field } from "../../../schema" +import { WidgetVO, widgetDTO, type IWidgetDTO } from "../../../widgets/widget.vo" import type { IViewDTO } from "../dto" import { ViewAggregateVO, viewAggregate, type IViewAggregate } from "../view-aggregate/view-aggregate.vo" import { ViewColor, viewColorGroup, type IRootViewColor } from "../view-color" @@ -52,6 +54,7 @@ export const baseViewDTO = z.object({ sort: viewSort.optional(), aggregate: viewAggregate.optional(), fields: viewFields.optional(), + widgets: widgetDTO.array().optional(), }) export type IBaseViewDTO = z.infer @@ -67,6 +70,7 @@ export abstract class AbstractView { sort: Option = None aggregate: Option = None fields: Option = None + widgets: Option = None abstract type: ViewType @@ -92,6 +96,20 @@ export abstract class AbstractView { if (dto.option) { this.setOption(dto.option) } + if (dto.widgets) { + this.setWidgets(dto.widgets) + } + } + + setWidgets(widgets: IWidgetDTO[]) { + this.widgets = Some(widgets.map((widget) => new WidgetVO(widget))) + } + + $createWidgetSpec(dto: IWidgetDTO): Option { + const widget = new WidgetVO(dto) + const previous = this.widgets.into(null) + const widgets = this.widgets.unwrapOr([]).concat(widget) + return Some(new WithViewWidgets(this.id, Option(previous), widgets)) } get showSystemFields() { @@ -247,6 +265,7 @@ export abstract class AbstractView { sort: this.sort.into(null)?.toJSON(), aggregate: this.aggregate.into(null)?.toJSON(), fields: this.fields.into(null)?.toJSON(), + widgets: this.widgets.into(undefined), } } } diff --git a/packages/table/src/modules/views/view/view-widget/index.ts b/packages/table/src/modules/views/view/view-widget/index.ts new file mode 100644 index 000000000..a97cde9a3 --- /dev/null +++ b/packages/table/src/modules/views/view/view-widget/index.ts @@ -0,0 +1 @@ +export * from "./view-widget.dto" diff --git a/packages/table/src/modules/views/view/view-widget/view-widget.dto.ts b/packages/table/src/modules/views/view/view-widget/view-widget.dto.ts new file mode 100644 index 000000000..bac4368b2 --- /dev/null +++ b/packages/table/src/modules/views/view/view-widget/view-widget.dto.ts @@ -0,0 +1,10 @@ +import { z } from "@undb/zod" +import { widgetDTO } from "../../../widgets/widget.vo" +import { viewId } from "../view-id.vo" + +export const createViewWidgetDTO = z.object({ + viewId: viewId, + widget: widgetDTO, +}) + +export type ICreateViewWidgetDTO = z.infer diff --git a/packages/table/src/modules/views/views.vo.ts b/packages/table/src/modules/views/views.vo.ts index 585339c3e..14abca4bc 100644 --- a/packages/table/src/modules/views/views.vo.ts +++ b/packages/table/src/modules/views/views.vo.ts @@ -1,4 +1,5 @@ import { and, andOptions, None, Option, ValueObject } from "@undb/domain" +import type { ICreateViewWidgetDTO } from ".." import type { ISetDefaultViewDTO } from "../../dto/set-default-view.dto" import { WithView, type TableComositeSpecification } from "../../specifications" import type { TableDo } from "../../table.do" @@ -90,6 +91,17 @@ export class Views extends ValueObject { return andOptions(...specs) } + $createWidget(table: TableDo, dto: ICreateViewWidgetDTO): Option { + const view = this.getViewById(dto.viewId) + const spec = view.$createWidgetSpec(dto.widget) + + if (spec.isSome()) { + spec.unwrap().mutate(table) + } + + return spec + } + $deleteField(table: TableDo, field: Field) { const specs = this.views.map((view) => view.$deleteField(table, field)) diff --git a/packages/table/src/modules/widgets/index.ts b/packages/table/src/modules/widgets/index.ts new file mode 100644 index 000000000..55207e37c --- /dev/null +++ b/packages/table/src/modules/widgets/index.ts @@ -0,0 +1 @@ +export * from "./widget.vo" diff --git a/packages/table/src/modules/widgets/widget-id.vo.ts b/packages/table/src/modules/widgets/widget-id.vo.ts new file mode 100644 index 000000000..c5883ba0e --- /dev/null +++ b/packages/table/src/modules/widgets/widget-id.vo.ts @@ -0,0 +1,12 @@ +import { IdFactory } from "@undb/domain" +import { z } from "@undb/zod" + +const prefix = "wid" +const size = 6 + +export const widgetId = z.string().startsWith(prefix) +export type IWidgetId = z.infer + +export const WidgetIdVo = IdFactory(prefix, size, widgetId) + +export type WidgetId = InstanceType diff --git a/packages/table/src/modules/widgets/widget-name.vo.ts b/packages/table/src/modules/widgets/widget-name.vo.ts new file mode 100644 index 000000000..f06db157c --- /dev/null +++ b/packages/table/src/modules/widgets/widget-name.vo.ts @@ -0,0 +1,12 @@ +import { ValueObject } from "@undb/domain" +import { z } from "@undb/zod" + +export const widgetName = z.string().min(2, { message: "widget name contains at least 2 chars" }) + +export type IWidgetName = z.infer + +export class WidgetNameVo extends ValueObject { + constructor(value: string) { + super({ value }) + } +} diff --git a/packages/table/src/modules/widgets/widget.vo.ts b/packages/table/src/modules/widgets/widget.vo.ts new file mode 100644 index 000000000..1609bef3d --- /dev/null +++ b/packages/table/src/modules/widgets/widget.vo.ts @@ -0,0 +1,61 @@ +import { ValueObject } from "@undb/domain" +import { z } from "@undb/zod" +import { tableId } from "../../table-id.vo" +import { chart } from "../chart/chart.vo" +import { widgetId, WidgetIdVo } from "./widget-id.vo" +import { widgetName } from "./widget-name.vo" + +const widgetItemChart = z.object({ + type: z.literal("chart"), + chart: chart, +}) + +const widgetItemTable = z.object({ + type: z.literal("table"), + tableId: tableId, +}) + +const widgetItem = z.discriminatedUnion("type", [widgetItemChart, widgetItemTable]) + +export const widgetDTO = z.object({ + id: widgetId, + name: widgetName, + item: widgetItem, +}) + +export type IWidgetDTO = z.infer + +export class WidgetVO extends ValueObject { + static default(name = "Count") { + return new WidgetVO({ + id: WidgetIdVo.create().value, + name: widgetName.parse(name), + item: { + type: "chart", + chart: { + type: "count", + config: {}, + }, + }, + }) + } + public get id() { + return this.props.id + } + + public get name() { + return this.props.name + } + + public get item() { + return this.props.item + } + + public toJSON(): IWidgetDTO { + return { + id: this.props.id, + name: this.props.name, + item: this.props.item, + } + } +} diff --git a/packages/table/src/specifications/table-view.specification.ts b/packages/table/src/specifications/table-view.specification.ts index f37a015e5..8f87af1bb 100644 --- a/packages/table/src/specifications/table-view.specification.ts +++ b/packages/table/src/specifications/table-view.specification.ts @@ -5,6 +5,7 @@ import type { IRootViewColor } from "../modules/views/view/view-color" import type { IViewFields } from "../modules/views/view/view-fields" import type { IViewOption } from "../modules/views/view/view-option.vo" import type { IViewSort } from "../modules/views/view/view-sort" +import type { IWidgetDTO } from "../modules/widgets/widget.vo" import type { TableDo } from "../table.do" import type { ITableSpecVisitor } from "./table-visitor.interface" import { TableComositeSpecification } from "./table.composite-specification" @@ -31,6 +32,28 @@ export class WithViewOption extends TableComositeSpecification { } } +export class WithViewWidgets extends TableComositeSpecification { + constructor( + public readonly viewId: ViewId, + public readonly previous: Option, + public readonly widgets: IWidgetDTO[], + ) { + super() + } + isSatisfiedBy(t: TableDo): boolean { + throw new WontImplementException(WithViewWidgets.name + ".isSatisfiedBy") + } + mutate(t: TableDo): Result { + const view = t.views.getViewById(this.viewId.value) + view.setWidgets(this.widgets) + return Ok(t) + } + accept(v: ITableSpecVisitor): Result { + v.withViewWidgets(this) + return Ok(undefined) + } +} + export class WithView extends TableComositeSpecification { constructor( public readonly previous: View, diff --git a/packages/table/src/specifications/table-visitor.interface.ts b/packages/table/src/specifications/table-visitor.interface.ts index a1e64941a..8c6399ebe 100644 --- a/packages/table/src/specifications/table-visitor.interface.ts +++ b/packages/table/src/specifications/table-visitor.interface.ts @@ -32,6 +32,7 @@ import type { WithViewIdSpecification, WithViewOption, WithViewSort, + WithViewWidgets, } from "./table-view.specification" import type { TableViewsSpecification } from "./table-views.specification" import type { DuplicatedTableSpecification } from "./table.specification" @@ -69,4 +70,5 @@ export interface ITableSpecVisitor extends ISpecVisitor { withTableForeignTables(spec: WithTableForeignTablesSpec): void withTableUnqueName(spec: TableUniqueNameSpecification): void withViewFieldWidth(spec: WithViewFieldWidth): void + withViewWidgets(spec: WithViewWidgets): void } diff --git a/packages/trpc/src/router.ts b/packages/trpc/src/router.ts index 4bb7cd8bd..8e498cd54 100644 --- a/packages/trpc/src/router.ts +++ b/packages/trpc/src/router.ts @@ -13,6 +13,7 @@ import { CreateTableFieldCommand, CreateTableFormCommand, CreateTableViewCommand, + CreateViewWidgetCommand, CreateWebhookCommand, DeleteBaseCommand, DeleteFormCommand, @@ -70,6 +71,7 @@ import { createTableFormCommandOutput, createTableViewCommand, createTableViewCommandOutput, + createViewWidgetCommand, createWebhookCommand, deleteBaseCommand, deleteFormCommand, @@ -112,9 +114,9 @@ import { updateaccountCommand, } from "@undb/commands" import { getCurrentSpaceId } from "@undb/context/server" -import { CommandBus, QueryBus } from "@undb/cqrs" +import { CommandBus,QueryBus } from "@undb/cqrs" import { container } from "@undb/di" -import type { ICommandBus, IQueryBus } from "@undb/domain" +import type { ICommandBus,IQueryBus } from "@undb/domain" import { CountRecordsQuery, GetAggregatesQuery, @@ -148,7 +150,7 @@ import { import { tableDTO } from "@undb/table" import { z } from "@undb/zod" import { authz } from "./authz.middleware" -import { privateProcedure, publicProcedure, t } from "./trpc" +import { privateProcedure,publicProcedure,t } from "./trpc" const commandBus = container.resolve(CommandBus) const queryBus = container.resolve(QueryBus) @@ -177,6 +179,13 @@ const formRouter = t.router({ .mutation(({ input }) => commandBus.execute(new SubmitFormCommand(input))), }) +const viewWidgetRouter = t.router({ + create: privateProcedure + .use(authz("view:update")) + .input(createViewWidgetCommand) + .mutation(({ input }) => commandBus.execute(new CreateViewWidgetCommand(input))), +}) + const viewRouter = t.router({ create: privateProcedure .use(authz("view:create")) @@ -228,6 +237,7 @@ const viewRouter = t.router({ .use(authz("view:update")) .input(setDefaultViewCommand) .mutation(({ input }) => commandBus.execute(new SetDefaultViewCommand(input))), + widget: viewWidgetRouter, }) const rlsRouter = t.router({ From 3b818fc06458a7d034dee666cde1d28e57b3fdc5 Mon Sep 17 00:00:00 2001 From: nichenqin Date: Thu, 10 Oct 2024 17:01:34 +0800 Subject: [PATCH 4/5] feat: chart --- .../blocks/aggregate/aggregate.svelte | 13 ++++++ .../blocks/aggregate/count-aggregate.svelte | 20 +++++++++ .../lib/components/blocks/chart/chart.svelte | 2 - .../blocks/chart/count-chart.svelte | 10 ----- .../create-reference-field-optioin.svelte | 2 +- .../field-options/rollup-field-option.svelte | 2 +- .../update-reference-field-optioin.svelte | 2 +- .../grid-view/grid-view-data-table.svelte | 2 +- .../foreign-records-picker-dropdown.svelte | 11 ++--- .../blocks/relations/relations.svelte | 2 +- .../blocks/table-tools/table-tools.svelte | 2 +- .../view-widget/view-widget-sheet.svelte | 4 +- .../components/blocks/widget/widget.svelte | 6 +-- apps/frontend/src/lib/store/table.store.ts | 4 +- .../(space)/t/[tableId]/+layout.svelte | 21 +++++----- .../(authed)/(space)/t/[tableId]/+layout.ts | 1 + .../s/b/[shareId]/t/[tableId]/+layout.svelte | 2 +- .../(share)/s/f/[shareId]/+layout.svelte | 2 +- .../(share)/s/v/[shareId]/+layout.svelte | 2 +- .../src/record/record.query-repository.ts | 24 +++++++---- .../src/methods/duplicate-table.method.ts | 2 +- .../src/modules/aggregate/aggregate.vo.ts | 41 +++++++++++++++++++ packages/table/src/modules/chart/chart.vo.ts | 14 +------ .../records/record/record.repository.ts | 9 +++- .../services/methods/get-aggregates.method.ts | 6 +-- .../variants/id-field/id-field.aggregate.ts | 3 ++ .../fields/variants/id-field/id-field.vo.ts | 3 +- .../table/src/modules/widgets/widget.vo.ts | 12 ++++-- packages/table/src/services/table.service.ts | 2 +- packages/table/src/table.factory.ts | 8 ++-- packages/template/src/template.factory.ts | 2 +- packages/template/src/templates/hr.base.json | 2 +- 32 files changed, 156 insertions(+), 82 deletions(-) create mode 100644 apps/frontend/src/lib/components/blocks/aggregate/aggregate.svelte create mode 100644 apps/frontend/src/lib/components/blocks/aggregate/count-aggregate.svelte delete mode 100644 apps/frontend/src/lib/components/blocks/chart/count-chart.svelte create mode 100644 packages/table/src/modules/aggregate/aggregate.vo.ts create mode 100644 packages/table/src/modules/schema/fields/variants/id-field/id-field.aggregate.ts diff --git a/apps/frontend/src/lib/components/blocks/aggregate/aggregate.svelte b/apps/frontend/src/lib/components/blocks/aggregate/aggregate.svelte new file mode 100644 index 000000000..d189edfa8 --- /dev/null +++ b/apps/frontend/src/lib/components/blocks/aggregate/aggregate.svelte @@ -0,0 +1,13 @@ + + + diff --git a/apps/frontend/src/lib/components/blocks/aggregate/count-aggregate.svelte b/apps/frontend/src/lib/components/blocks/aggregate/count-aggregate.svelte new file mode 100644 index 000000000..33008f174 --- /dev/null +++ b/apps/frontend/src/lib/components/blocks/aggregate/count-aggregate.svelte @@ -0,0 +1,20 @@ + + + +
+ 50 +
+
diff --git a/apps/frontend/src/lib/components/blocks/chart/chart.svelte b/apps/frontend/src/lib/components/blocks/chart/chart.svelte index 26b199835..3cbb610a5 100644 --- a/apps/frontend/src/lib/components/blocks/chart/chart.svelte +++ b/apps/frontend/src/lib/components/blocks/chart/chart.svelte @@ -1,12 +1,10 @@ diff --git a/apps/frontend/src/lib/components/blocks/chart/count-chart.svelte b/apps/frontend/src/lib/components/blocks/chart/count-chart.svelte deleted file mode 100644 index 8f6ae83f6..000000000 --- a/apps/frontend/src/lib/components/blocks/chart/count-chart.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - -
- 50 -
-
diff --git a/apps/frontend/src/lib/components/blocks/field-options/create-reference-field-optioin.svelte b/apps/frontend/src/lib/components/blocks/field-options/create-reference-field-optioin.svelte index 59e312ee2..77cdd585c 100644 --- a/apps/frontend/src/lib/components/blocks/field-options/create-reference-field-optioin.svelte +++ b/apps/frontend/src/lib/components/blocks/field-options/create-reference-field-optioin.svelte @@ -43,7 +43,7 @@ $: ft = $getForeignTableStore.data?.table // @ts-ignore - $: foreignTable = ft ? TableFactory.fromJSON(ft) : undefined + $: foreignTable = ft ? new TableFactory().fromJSON(ft) : undefined const value = writable | undefined>() $: if ($value && foreignTable) { diff --git a/apps/frontend/src/lib/components/blocks/field-options/rollup-field-option.svelte b/apps/frontend/src/lib/components/blocks/field-options/rollup-field-option.svelte index b48206e40..c97f75a44 100644 --- a/apps/frontend/src/lib/components/blocks/field-options/rollup-field-option.svelte +++ b/apps/frontend/src/lib/components/blocks/field-options/rollup-field-option.svelte @@ -34,7 +34,7 @@ $: foreignTable = $store.data?.table const foreignTableDo = writable() - $: if (foreignTable) foreignTableDo.set(TableFactory.fromJSON(foreignTable)) + $: if (foreignTable) foreignTableDo.set(new TableFactory().fromJSON(foreignTable)) $: schema = foreignTable?.schema $: fields = schema?.filter((f) => getIsFieldCanBeRollup(f.type)) diff --git a/apps/frontend/src/lib/components/blocks/field-options/update-reference-field-optioin.svelte b/apps/frontend/src/lib/components/blocks/field-options/update-reference-field-optioin.svelte index 5e0f6b7f1..c35f6cf95 100644 --- a/apps/frontend/src/lib/components/blocks/field-options/update-reference-field-optioin.svelte +++ b/apps/frontend/src/lib/components/blocks/field-options/update-reference-field-optioin.svelte @@ -43,7 +43,7 @@ $: ft = $getForeignTableStore.data?.table // @ts-ignore - $: foreignTable = ft ? TableFactory.fromJSON(ft) : undefined + $: foreignTable = ft ? new TableFactory().fromJSON(ft) : undefined const value = writable | undefined>() onMount(() => { diff --git a/apps/frontend/src/lib/components/blocks/grid-view/grid-view-data-table.svelte b/apps/frontend/src/lib/components/blocks/grid-view/grid-view-data-table.svelte index e9767214f..feeeb0423 100644 --- a/apps/frontend/src/lib/components/blocks/grid-view/grid-view-data-table.svelte +++ b/apps/frontend/src/lib/components/blocks/grid-view/grid-view-data-table.svelte @@ -45,7 +45,7 @@ const t = getTable() - let fields = derived(t, ($t) => $t?.getOrderedVisibleFields($viewId) ?? ([] as Field[])) + let fields = derived([t, viewId], ([$t, $viewId]) => $t?.getOrderedVisibleFields($viewId) ?? ([] as Field[])) $: perPage = $preferences.gridViewPerPage ?? 50 diff --git a/apps/frontend/src/lib/components/blocks/reference/foreign-records-picker-dropdown.svelte b/apps/frontend/src/lib/components/blocks/reference/foreign-records-picker-dropdown.svelte index b944c3594..77fb8dab0 100644 --- a/apps/frontend/src/lib/components/blocks/reference/foreign-records-picker-dropdown.svelte +++ b/apps/frontend/src/lib/components/blocks/reference/foreign-records-picker-dropdown.svelte @@ -2,7 +2,7 @@ import { GetForeignTableStore } from "$houdini" import * as Popover from "$lib/components/ui/popover" import ForeignRecordsPicker from "./foreign-records-picker.svelte" - import { readable, writable } from "svelte/store" + import { derived, readable, writable } from "svelte/store" import { ReferenceField, TableFactory } from "@undb/table" import Button from "$lib/components/ui/button/button.svelte" import { LoaderCircleIcon } from "lucide-svelte" @@ -28,9 +28,10 @@ $: if (open) foreignTableStore.fetch({ variables: { tableId: foreignTableId } }) - $: table = $foreignTableStore.data?.table - - $: foreignTable = table ? readable(TableFactory.fromJSON(table)) : null + const foreignTable = derived(foreignTableStore, ($foreignTableStore) => { + const table = $foreignTableStore.data?.table + return table ? new TableFactory().fromJSON(table) : null + }) @@ -46,7 +47,7 @@ {/if} - {#if foreignTable} + {#if $foreignTable} TableFactory.fromJSON(table)) + $: foreignTables = foreignTablesDTO.map((table) => new TableFactory().fromJSON(table)) diff --git a/apps/frontend/src/lib/components/blocks/table-tools/table-tools.svelte b/apps/frontend/src/lib/components/blocks/table-tools/table-tools.svelte index 8b7970d14..aab332c2d 100644 --- a/apps/frontend/src/lib/components/blocks/table-tools/table-tools.svelte +++ b/apps/frontend/src/lib/components/blocks/table-tools/table-tools.svelte @@ -34,6 +34,6 @@ {/if} - + diff --git a/apps/frontend/src/lib/components/blocks/view-widget/view-widget-sheet.svelte b/apps/frontend/src/lib/components/blocks/view-widget/view-widget-sheet.svelte index 360ecb502..0708d4cc6 100644 --- a/apps/frontend/src/lib/components/blocks/view-widget/view-widget-sheet.svelte +++ b/apps/frontend/src/lib/components/blocks/view-widget/view-widget-sheet.svelte @@ -15,7 +15,7 @@ export let viewId: Readable const view = derived([viewId], ([$viewId]) => $table?.views.getViewById($viewId)) - const widgets = derived([view], ([$view]) => $view?.widgets ?? []) + const widgets = derived([view], ([$view]) => $view?.widgets.unwrapOr([]) ?? []) const createViewWidgetMutation = createMutation({ mutationFn: trpc.table.view.widget.create.mutate, @@ -36,7 +36,7 @@ {/each} - diff --git a/apps/frontend/src/lib/components/blocks/widget/widget.svelte b/apps/frontend/src/lib/components/blocks/widget/widget.svelte index b7e86a3f0..bf3f288fa 100644 --- a/apps/frontend/src/lib/components/blocks/widget/widget.svelte +++ b/apps/frontend/src/lib/components/blocks/widget/widget.svelte @@ -1,12 +1,12 @@
- {#if widget.item.type === "chart"} - + {#if widget.item.type === "aggregate"} + {/if}
diff --git a/apps/frontend/src/lib/store/table.store.ts b/apps/frontend/src/lib/store/table.store.ts index cbb3a64e2..c680e22b6 100644 --- a/apps/frontend/src/lib/store/table.store.ts +++ b/apps/frontend/src/lib/store/table.store.ts @@ -1,7 +1,7 @@ import { page } from "$app/stores" import type { TableDo } from "@undb/table" -import { getContext, setContext } from "svelte" -import { derived, type Writable } from "svelte/store" +import { getContext,setContext } from "svelte" +import { derived,type Writable } from "svelte/store" export function setTable(table: Writable) { setContext("table", table) diff --git a/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.svelte b/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.svelte index 9b89625bc..d19ca1059 100644 --- a/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.svelte +++ b/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.svelte @@ -3,38 +3,37 @@ import { TableFactory, TableDo } from "@undb/table" import { setTable } from "$lib/store/table.store" import type { LayoutData } from "./$types" - import { writable } from "svelte/store" + import { derived, writable } from "svelte/store" import { shareStore } from "$lib/store/share.store" import { aggregatesStore } from "$lib/store/aggregates.store" export let data: LayoutData - $: tableStore = data.tableStore + let tableStore = data.tableStore - $: fetching = $tableStore.fetching - $: tableDTO = $tableStore.data?.table + let tableDTO = derived(tableStore, ($tableStore) => $tableStore.data?.table) const table = writable() - $: { - if (!fetching && tableDTO && $page.params.tableId === tableDTO.id) { - table.set(TableFactory.fromJSON(tableDTO)) + $: if ($page.params.tableId === $tableDTO?.id) { + if ($tableDTO) { + table.set(new TableFactory().fromJSON($tableDTO)) setTable(table) } } - $: if (tableDTO) { - for (const view of tableDTO.views) { + $: if ($tableDTO) { + for (const view of $tableDTO.views) { if (view.share) { shareStore.set(view.id, { ...view.share, target: { type: "view", id: view.id } }) } } - for (const form of tableDTO.forms ?? []) { + for (const form of $tableDTO.forms ?? []) { if (form?.share) { shareStore.set(form.id, { ...form.share, target: { type: "form", id: form.id } }) } } - aggregatesStore.updateTableAggregates(tableDTO.id, tableDTO.viewData?.aggregate ?? {}) + aggregatesStore.updateTableAggregates($tableDTO.id, $tableDTO.viewData?.aggregate ?? {}) } diff --git a/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.ts b/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.ts index 2887d54ac..d4f5eab89 100644 --- a/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.ts +++ b/apps/frontend/src/routes/(authed)/(space)/t/[tableId]/+layout.ts @@ -18,5 +18,6 @@ export const load: LayoutLoad = async (event) => { return { tableStore: store, + table: data.data?.table, } } diff --git a/apps/frontend/src/routes/(share)/s/b/[shareId]/t/[tableId]/+layout.svelte b/apps/frontend/src/routes/(share)/s/b/[shareId]/t/[tableId]/+layout.svelte index aa4cc5ea6..887da36d6 100644 --- a/apps/frontend/src/routes/(share)/s/b/[shareId]/t/[tableId]/+layout.svelte +++ b/apps/frontend/src/routes/(share)/s/b/[shareId]/t/[tableId]/+layout.svelte @@ -18,7 +18,7 @@ $: { if (!fetching && tableDTO) { // @ts-ignore - table.set(TableFactory.fromJSON(tableDTO)) + table.set(new TableFactory().fromJSON(tableDTO)) setTable(table) } } diff --git a/apps/frontend/src/routes/(share)/s/f/[shareId]/+layout.svelte b/apps/frontend/src/routes/(share)/s/f/[shareId]/+layout.svelte index 6aff79cc0..d8d2801cd 100644 --- a/apps/frontend/src/routes/(share)/s/f/[shareId]/+layout.svelte +++ b/apps/frontend/src/routes/(share)/s/f/[shareId]/+layout.svelte @@ -16,7 +16,7 @@ const table = writable() $: { if (!fetching && tableDTO) { - table.set(TableFactory.fromJSON(tableDTO)) + table.set(new TableFactory().fromJSON(tableDTO)) setTable(table) } } diff --git a/apps/frontend/src/routes/(share)/s/v/[shareId]/+layout.svelte b/apps/frontend/src/routes/(share)/s/v/[shareId]/+layout.svelte index 580171790..93a35ee8b 100644 --- a/apps/frontend/src/routes/(share)/s/v/[shareId]/+layout.svelte +++ b/apps/frontend/src/routes/(share)/s/v/[shareId]/+layout.svelte @@ -18,7 +18,7 @@ const table = writable() $: { if (!fetching && tableDTO) { - table.set(TableFactory.fromJSON(tableDTO)) + table.set(new TableFactory().fromJSON(tableDTO)) setTable(table) } } diff --git a/packages/persistence/src/record/record.query-repository.ts b/packages/persistence/src/record/record.query-repository.ts index 512c360a5..ee8d959bd 100644 --- a/packages/persistence/src/record/record.query-repository.ts +++ b/packages/persistence/src/record/record.query-repository.ts @@ -12,6 +12,7 @@ import { type IRecordDTO, type IRecordQueryRepository, type ITableRepository, + type IViewAggregate, type IViewSort, type QueryArgs, type RecordId, @@ -129,6 +130,7 @@ export class RecordQueryRepository implements IRecordQueryRepository { async aggregate( table: TableDo, viewId: Option, + aggregate: Option, query: Option, ): Promise> { const context = executionContext.getStore() @@ -136,13 +138,19 @@ export class RecordQueryRepository implements IRecordQueryRepository { const t = new UnderlyingTable(table) const view = table.views.getViewById(viewId.into(undefined)?.value) - const aggregates = view.aggregate - if (aggregates.isNone()) { - return {} + if (aggregate.isNone()) { + const aggregates = view.aggregate + if (aggregates.isNone()) { + return {} + } + const aggs = aggregates.unwrap() + if (aggs.isEmpty()) { + return {} + } + + aggregate = Some(aggs.toJSON()) } - - const aggs = aggregates.unwrap() - if (aggs.isEmpty()) { + if (aggregate.isNone()) { return {} } @@ -168,7 +176,7 @@ export class RecordQueryRepository implements IRecordQueryRepository { .select((eb) => { const ebs: AliasedExpression[] = [] - for (const [fieldId, fieldAggregate] of aggs) { + for (const [fieldId, fieldAggregate] of Object.entries(aggregate.unwrap())) { if (!fieldAggregate) { continue } @@ -185,7 +193,7 @@ export class RecordQueryRepository implements IRecordQueryRepository { .where((eb) => { const ebs: Expression[] = [] - for (const [fieldId, fieldAggregate] of aggs) { + for (const [fieldId, fieldAggregate] of Object.entries(aggregate.unwrap())) { if (!fieldAggregate) { continue } diff --git a/packages/table/src/methods/duplicate-table.method.ts b/packages/table/src/methods/duplicate-table.method.ts index 5f714fce2..9d1e0c2a3 100644 --- a/packages/table/src/methods/duplicate-table.method.ts +++ b/packages/table/src/methods/duplicate-table.method.ts @@ -10,7 +10,7 @@ export function duplicateTableMethod( dto: IDuplicateTableDTO, tableNames: string[], ): DuplicatedTableSpecification { - const duplicated = TableFactory.fromJSON({ + const duplicated = new TableFactory().fromJSON({ ...this.toJSON(), id: dto.tableId ?? TableIdVo.create().value, baseId: dto.baseId ?? this.baseId, diff --git a/packages/table/src/modules/aggregate/aggregate.vo.ts b/packages/table/src/modules/aggregate/aggregate.vo.ts new file mode 100644 index 000000000..c17ed3053 --- /dev/null +++ b/packages/table/src/modules/aggregate/aggregate.vo.ts @@ -0,0 +1,41 @@ +import { ValueObject } from "@undb/domain" +import { z } from "@undb/zod" +import { createConditionGroup } from "../schema/fields/condition/condition.type" + +const aggregateCondition = z.undefined() +const aggregateConditionGroup = createConditionGroup(aggregateCondition, aggregateCondition) + +export const countAggregate = z.object({ + type: z.literal("count"), + config: z.object({ + condition: aggregateConditionGroup.optional(), + }), +}) + +export const sumAggregate = z.object({ + type: z.literal("sum"), +}) + +export const avgAggregate = z.object({ + type: z.literal("avg"), +}) + +export const maxAggregate = z.object({ + type: z.literal("max"), +}) + +export const minAggregate = z.object({ + type: z.literal("min"), +}) + +export const aggregate = z.discriminatedUnion("type", [ + countAggregate, + sumAggregate, + avgAggregate, + maxAggregate, + minAggregate, +]) + +export type IAggregate = z.infer + +export class AggregateVO extends ValueObject {} diff --git a/packages/table/src/modules/chart/chart.vo.ts b/packages/table/src/modules/chart/chart.vo.ts index 6a9b17067..9bd2facb4 100644 --- a/packages/table/src/modules/chart/chart.vo.ts +++ b/packages/table/src/modules/chart/chart.vo.ts @@ -1,18 +1,6 @@ import { ValueObject } from "@undb/domain" import { z } from "@undb/zod" import { fieldId } from "../schema" -import { createConditionGroup } from "../schema/fields/condition/condition.type" - -const chartFilterOption = z.undefined() - -export const chartFilterGroup = createConditionGroup(chartFilterOption, chartFilterOption) - -const countChart = z.object({ - type: z.literal("count"), - config: z.object({ - condition: chartFilterGroup.optional(), - }), -}) const pieChart = z.object({ type: z.literal("pie"), @@ -34,7 +22,7 @@ const barChart = z.object({ }), }) -export const chart = z.discriminatedUnion("type", [countChart, pieChart, barChart]) +export const chart = z.discriminatedUnion("type", [pieChart, barChart]) export type IChart = z.infer diff --git a/packages/table/src/modules/records/record/record.repository.ts b/packages/table/src/modules/records/record/record.repository.ts index 62d46021d..bdbb94768 100644 --- a/packages/table/src/modules/records/record/record.repository.ts +++ b/packages/table/src/modules/records/record/record.repository.ts @@ -2,7 +2,7 @@ import { None, Some, andOptions, type IPagination, type Option, type PaginatedDT import type { TableId } from "../../../table-id.vo" import type { TableDo } from "../../../table.do" import { getSpec } from "../../schema/fields/condition" -import type { View, ViewId } from "../../views" +import type { IViewAggregate, View, ViewId } from "../../views" import type { AggregateResult, ICountRecordsDTO, IGetRecordsDTO } from "../dto" import { withQ } from "../specification/with-q.specification" import type { IRecordDTO } from "./dto" @@ -51,7 +51,12 @@ export interface IRecordQueryRepository { count(tableId: TableId): Promise countWhere(table: TableDo, spec: Option): Promise - aggregate(table: TableDo, viewId: Option, query: Option): Promise> + aggregate( + table: TableDo, + viewId: Option, + aggregate: Option, + query: Option, + ): Promise> } export function buildQuery(table: TableDo, dto: IGetRecordsDTO): Option { diff --git a/packages/table/src/modules/records/services/methods/get-aggregates.method.ts b/packages/table/src/modules/records/services/methods/get-aggregates.method.ts index 16bd9e1f5..c8f8d121a 100644 --- a/packages/table/src/modules/records/services/methods/get-aggregates.method.ts +++ b/packages/table/src/modules/records/services/methods/get-aggregates.method.ts @@ -1,7 +1,7 @@ -import { None, Some } from "@undb/domain" +import { None,Some } from "@undb/domain" import { TableIdVo } from "../../../../table-id.vo" import { ViewIdVo } from "../../../views" -import type { AggregateResult, IGetAggregatesDTO } from "../../dto" +import type { AggregateResult,IGetAggregatesDTO } from "../../dto" import type { RecordsQueryService } from "../records.query-service" export async function getAggregates( @@ -12,5 +12,5 @@ export async function getAggregates( const table = (await this.tableRepository.findOneById(tableId)).expect("Table not found") const viewId = dto.viewId ? Some(new ViewIdVo(dto.viewId)) : None - return this.repo.aggregate(table, viewId, None) + return this.repo.aggregate(table, viewId, None, None) } diff --git a/packages/table/src/modules/schema/fields/variants/id-field/id-field.aggregate.ts b/packages/table/src/modules/schema/fields/variants/id-field/id-field.aggregate.ts new file mode 100644 index 000000000..99032973d --- /dev/null +++ b/packages/table/src/modules/schema/fields/variants/id-field/id-field.aggregate.ts @@ -0,0 +1,3 @@ +import { z } from "@undb/zod" + +export const idFieldAggregate = z.enum(["count"]) diff --git a/packages/table/src/modules/schema/fields/variants/id-field/id-field.vo.ts b/packages/table/src/modules/schema/fields/variants/id-field/id-field.vo.ts index 84344152c..36bcc8915 100644 --- a/packages/table/src/modules/schema/fields/variants/id-field/id-field.vo.ts +++ b/packages/table/src/modules/schema/fields/variants/id-field/id-field.vo.ts @@ -7,6 +7,7 @@ import type { IFieldVisitor } from "../../field.visitor" import { AbstractField, baseFieldDTO, createBaseFieldDTO } from "../abstract-field.vo" import { IdEqual, IdIn } from "./id-field-value.specification" import { IdFieldValue } from "./id-field-value.vo" +import { idFieldAggregate } from "./id-field.aggregate" import { createIdFieldCondition, type IIdFieldCondition, type IIdFieldConditionSchema } from "./id-field.condition" export const ID_TYPE = "id" as const @@ -69,6 +70,6 @@ export class IdField extends AbstractField { } override get aggregate() { - return z.undefined() + return idFieldAggregate } } diff --git a/packages/table/src/modules/widgets/widget.vo.ts b/packages/table/src/modules/widgets/widget.vo.ts index 1609bef3d..4e8d94542 100644 --- a/packages/table/src/modules/widgets/widget.vo.ts +++ b/packages/table/src/modules/widgets/widget.vo.ts @@ -1,10 +1,16 @@ import { ValueObject } from "@undb/domain" import { z } from "@undb/zod" import { tableId } from "../../table-id.vo" +import { aggregate } from "../aggregate/aggregate.vo" import { chart } from "../chart/chart.vo" import { widgetId, WidgetIdVo } from "./widget-id.vo" import { widgetName } from "./widget-name.vo" +const widgetItemAggregate = z.object({ + type: z.literal("aggregate"), + aggregate: aggregate, +}) + const widgetItemChart = z.object({ type: z.literal("chart"), chart: chart, @@ -15,7 +21,7 @@ const widgetItemTable = z.object({ tableId: tableId, }) -const widgetItem = z.discriminatedUnion("type", [widgetItemChart, widgetItemTable]) +const widgetItem = z.discriminatedUnion("type", [widgetItemAggregate, widgetItemChart, widgetItemTable]) export const widgetDTO = z.object({ id: widgetId, @@ -31,8 +37,8 @@ export class WidgetVO extends ValueObject { id: WidgetIdVo.create().value, name: widgetName.parse(name), item: { - type: "chart", - chart: { + type: "aggregate", + aggregate: { type: "count", config: {}, }, diff --git a/packages/table/src/services/table.service.ts b/packages/table/src/services/table.service.ts index f533b88e6..0f936453b 100644 --- a/packages/table/src/services/table.service.ts +++ b/packages/table/src/services/table.service.ts @@ -73,7 +73,7 @@ export class TableService implements ITableService { readonly logger = createLogger(TableService.name) public get creator() { - return TableFactory + return new TableFactory() } constructor( diff --git a/packages/table/src/table.factory.ts b/packages/table/src/table.factory.ts index bb2a88a2e..aa779bf86 100644 --- a/packages/table/src/table.factory.ts +++ b/packages/table/src/table.factory.ts @@ -49,11 +49,11 @@ class TablesIdsMap { } export class TableFactory { - private static get builder() { + private get builder() { return new TableBuilder() } - static create(dto: ICreateTableDTO): TableDo { + create(dto: ICreateTableDTO): TableDo { dto = createTableDTO.parse(dto) const table = this.builder .setId(dto.id) @@ -73,7 +73,7 @@ export class TableFactory { } // create many table inside a base - static createMany( + createMany( baseNames: string[], base: Base, dtos: ICreateTablesDTO[], @@ -174,7 +174,7 @@ export class TableFactory { }) } - static fromJSON(dto: ITableDTO): TableDo { + fromJSON(dto: ITableDTO): TableDo { return this.builder .setId(dto.id) .setBaseId(dto.baseId) diff --git a/packages/template/src/template.factory.ts b/packages/template/src/template.factory.ts index 50b2642b9..983597f1b 100644 --- a/packages/template/src/template.factory.ts +++ b/packages/template/src/template.factory.ts @@ -58,7 +58,7 @@ export class TemplateFactory { } }) as ICreateTablesDTO[] - const tables = TableFactory.createMany(baseNames, base, dtos) + const tables = new TableFactory().createMany(baseNames, base, dtos) result.push({ base, tables }) } diff --git a/packages/template/src/templates/hr.base.json b/packages/template/src/templates/hr.base.json index 4592bee12..e0629c60e 100644 --- a/packages/template/src/templates/hr.base.json +++ b/packages/template/src/templates/hr.base.json @@ -82,7 +82,7 @@ } }, "Department Name": { - "id": "department_name", + "id": "employee_department_name", "type": "rollup", "option": { "referenceFieldId": "department", From 4eac4734b91945b98d5a6a2b1146f47b93484ab5 Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Thu, 10 Oct 2024 09:02:41 +0000 Subject: [PATCH 5/5] Prepare release v1.0.0-98 --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4230a9d3..be6ee75b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## v1.0.0-98 + ## v1.0.0-97 diff --git a/package.json b/package.json index c81a14e77..edbcad883 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "undb", - "version": "1.0.0-97", + "version": "1.0.0-98", "private": true, "scripts": { "build": "NODE_ENV=production bun --bun turbo build",