From ffeee1736584dd900cbfb00c5109342d13dcb4ab Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Mon, 4 Aug 2025 18:04:32 -0600 Subject: [PATCH] move enum types --- .vscode/buster.code-workspace | 3 +- apps/api/libs/database/src/enums.rs | 2 +- .../src/api/v2/reports/[id]/sharing/PUT.ts | 8 +--- .../api/buster_rest/collections/requests.ts | 5 +- .../api/buster_rest/dashboards/requests.ts | 5 +- .../src/api/buster_rest/metrics/requests.ts | 3 +- .../api/buster_rest/reports/queryRequests.ts | 4 -- .../features/ShareMenu/AccessDropdown.tsx | 48 +++++++++++-------- .../ShareMenu/ShareMenuContent.stories.tsx | 6 +-- .../ShareMenu/ShareMenuContentBody.tsx | 2 +- .../metrics/StatusBadgeIndicator/helpers.ts | 12 ++--- apps/web/src/lib/share.ts | 8 ++-- packages/server-shared/package.json | 34 ++++++------- .../src/metrics/requests.types.ts | 4 -- packages/server-shared/src/reports/index.ts | 1 + .../src/reports/reports.types.ts | 2 +- .../server-shared/src/reports/requests.ts | 10 ---- packages/server-shared/src/share/assets.ts | 17 +++++++ packages/server-shared/src/share/index.ts | 2 + .../src/share/individual-permissions.ts | 16 +++++++ packages/server-shared/src/share/requests.ts | 25 +++++++++- .../src/share/share-interfaces.test.ts | 20 ++++---- .../src/share/share-interfaces.types.ts | 36 +++++++++++--- .../src/share/verification.types.ts | 23 +++++---- .../src/shared-permissions/index.ts | 43 ----------------- packages/server-utils/.gitignore | 38 +++++++++++++++ packages/server-utils/README.md | 7 +++ packages/server-utils/biome.json | 7 +++ packages/server-utils/env.d.ts | 10 ++++ packages/server-utils/package.json | 40 ++++++++++++++++ packages/server-utils/scripts/validate-env.ts | 22 +++++++++ .../src}/report/MarkdownPlugin.ts | 0 .../src}/report/callout-serializer.ts | 0 .../lib => server-utils/src}/report/index.ts | 0 .../src}/report/markdown-to-platejs.test.ts | 0 .../src}/report/markdown-to-platejs.ts | 0 packages/server-utils/tsconfig.json | 10 ++++ packages/server-utils/vitest.config.ts | 3 ++ pnpm-lock.yaml | 39 ++++++++++----- 39 files changed, 345 insertions(+), 170 deletions(-) create mode 100644 packages/server-shared/src/share/assets.ts create mode 100644 packages/server-shared/src/share/individual-permissions.ts delete mode 100644 packages/server-shared/src/shared-permissions/index.ts create mode 100644 packages/server-utils/.gitignore create mode 100644 packages/server-utils/README.md create mode 100644 packages/server-utils/biome.json create mode 100644 packages/server-utils/env.d.ts create mode 100644 packages/server-utils/package.json create mode 100644 packages/server-utils/scripts/validate-env.ts rename packages/{server-shared/src/lib => server-utils/src}/report/MarkdownPlugin.ts (100%) rename packages/{server-shared/src/lib => server-utils/src}/report/callout-serializer.ts (100%) rename packages/{server-shared/src/lib => server-utils/src}/report/index.ts (100%) rename packages/{server-shared/src/lib => server-utils/src}/report/markdown-to-platejs.test.ts (100%) rename packages/{server-shared/src/lib => server-utils/src}/report/markdown-to-platejs.ts (100%) create mode 100644 packages/server-utils/tsconfig.json create mode 100644 packages/server-utils/vitest.config.ts diff --git a/.vscode/buster.code-workspace b/.vscode/buster.code-workspace index bfecd5323..060cd5401 100644 --- a/.vscode/buster.code-workspace +++ b/.vscode/buster.code-workspace @@ -19,7 +19,8 @@ { "path": "../packages/vitest-config" }, { "path": "../packages/web-tools" }, { "path": "../packages/sandbox" }, - { "path": "../packages/env-utils" } + { "path": "../packages/env-utils" }, + { "path": "../packages/server-utils" } ], "settings": { "editor.defaultFormatter": "biomejs.biome", diff --git a/apps/api/libs/database/src/enums.rs b/apps/api/libs/database/src/enums.rs index e34b31dfa..30c42fb03 100644 --- a/apps/api/libs/database/src/enums.rs +++ b/apps/api/libs/database/src/enums.rs @@ -742,7 +742,7 @@ impl FromSql for MessageFeedback { Serialize, )] #[diesel(sql_type = sql_types::WorkspaceSharingEnum)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "snake_case")] pub enum WorkspaceSharing { #[serde(alias = "none")] None, diff --git a/apps/server/src/api/v2/reports/[id]/sharing/PUT.ts b/apps/server/src/api/v2/reports/[id]/sharing/PUT.ts index c928d3faf..434e7c6cb 100644 --- a/apps/server/src/api/v2/reports/[id]/sharing/PUT.ts +++ b/apps/server/src/api/v2/reports/[id]/sharing/PUT.ts @@ -1,10 +1,6 @@ import { getUserOrganizationId, updateReport } from '@buster/database'; -import { - type ShareUpdateRequest, - ShareUpdateRequestSchema, - type ShareUpdateResponse, - type UpdateReportResponse, -} from '@buster/server-shared/reports'; +import type { ShareUpdateResponse, UpdateReportResponse } from '@buster/server-shared/reports'; +import { type ShareUpdateRequest, ShareUpdateRequestSchema } from '@buster/server-shared/share'; import { zValidator } from '@hono/zod-validator'; import { Hono } from 'hono'; import { HTTPException } from 'hono/http-exception'; diff --git a/apps/web/src/api/buster_rest/collections/requests.ts b/apps/web/src/api/buster_rest/collections/requests.ts index 6e9140765..03ae5e840 100644 --- a/apps/web/src/api/buster_rest/collections/requests.ts +++ b/apps/web/src/api/buster_rest/collections/requests.ts @@ -1,11 +1,8 @@ import type { ShareAssetType } from '@buster/server-shared/share'; import type { BusterCollection, BusterCollectionListItem } from '@/api/asset_interfaces/collection'; -import type { - ShareDeleteRequest, - ShareUpdateRequest -} from '@/api/asset_interfaces/shared_interfaces'; import mainApi from '@/api/buster_rest/instances'; import { SharePostRequest } from '@buster/server-shared/share'; +import type { ShareDeleteRequest, ShareUpdateRequest } from '@buster/server-shared/share'; export const collectionsGetList = async (params: { /** Current page number (1-based indexing) */ diff --git a/apps/web/src/api/buster_rest/dashboards/requests.ts b/apps/web/src/api/buster_rest/dashboards/requests.ts index 7be43330f..a2919fc1e 100644 --- a/apps/web/src/api/buster_rest/dashboards/requests.ts +++ b/apps/web/src/api/buster_rest/dashboards/requests.ts @@ -3,10 +3,7 @@ import type { BusterDashboardResponse, DashboardConfig } from '@/api/asset_interfaces/dashboard'; -import type { - ShareDeleteRequest, - ShareUpdateRequest -} from '@/api/asset_interfaces/shared_interfaces'; +import type { ShareDeleteRequest, ShareUpdateRequest } from '@buster/server-shared/share'; import mainApi from '@/api/buster_rest/instances'; import { serverFetch } from '@/api/createServerInstance'; import { SharePostRequest } from '@buster/server-shared/share'; diff --git a/apps/web/src/api/buster_rest/metrics/requests.ts b/apps/web/src/api/buster_rest/metrics/requests.ts index bbd64cc7e..730246a39 100644 --- a/apps/web/src/api/buster_rest/metrics/requests.ts +++ b/apps/web/src/api/buster_rest/metrics/requests.ts @@ -1,8 +1,6 @@ import type { BulkUpdateMetricVerificationStatusRequest, BulkUpdateMetricVerificationStatusResponse, - ShareDeleteRequest, - ShareUpdateRequest, DeleteMetricRequest, DeleteMetricResponse, DuplicateMetricRequest, @@ -18,6 +16,7 @@ import type { UpdateMetricResponse, ShareUpdateResponse } from '@buster/server-shared/metrics'; +import type { ShareDeleteRequest, ShareUpdateRequest } from '@buster/server-shared/share'; import { serverFetch } from '@/api/createServerInstance'; import { mainApi } from '../instances'; import { SharePostRequest } from '@buster/server-shared/share'; diff --git a/apps/web/src/api/buster_rest/reports/queryRequests.ts b/apps/web/src/api/buster_rest/reports/queryRequests.ts index 5f67f6b6e..2844cab33 100644 --- a/apps/web/src/api/buster_rest/reports/queryRequests.ts +++ b/apps/web/src/api/buster_rest/reports/queryRequests.ts @@ -10,7 +10,6 @@ import { useMemoizedFn } from '@/hooks'; import { queryKeys } from '@/api/query_keys'; import type { RustApiError } from '../errors'; import type { - GetReportsListResponse, GetReportIndividualResponse, UpdateReportRequest, UpdateReportResponse @@ -129,9 +128,6 @@ export const useUpdateReport = () => { queryKeys.reportsGetReport(reportId).queryKey, create(previousReport, (draft) => { if (data.name !== undefined) draft.name = data.name; - if (data.description !== undefined) draft.description = data.description; - if (data.publicly_accessible !== undefined) - draft.publicly_accessible = data.publicly_accessible; if (data.content !== undefined) draft.content = data.content; }) ); diff --git a/apps/web/src/components/features/ShareMenu/AccessDropdown.tsx b/apps/web/src/components/features/ShareMenu/AccessDropdown.tsx index c46091a35..b81794d5c 100644 --- a/apps/web/src/components/features/ShareMenu/AccessDropdown.tsx +++ b/apps/web/src/components/features/ShareMenu/AccessDropdown.tsx @@ -64,16 +64,20 @@ export const AccessDropdown: React.FC = React.memo( // Using a type-safe switch to handle all ShareRole values switch (value) { - case 'fullAccess': + case 'full_access': return 'Full access'; - case 'canEdit': + case 'can_edit': return 'Can edit'; - case 'canView': + case 'can_view': return 'Can view'; case 'owner': return 'Owner'; case 'remove': return 'Remove'; + case 'viewer': + return 'Viewer'; + case 'can_filter': + return 'Can filter'; case 'none': return 'Not shared'; default: @@ -86,7 +90,13 @@ export const AccessDropdown: React.FC = React.memo( if (value === 'remove' || value === 'notShared') { onChangeShareLevel?.(null); } else { - onChangeShareLevel?.(value as ShareRole); + if (props.type === 'workspace') { + (onChangeShareLevel as (level: WorkspaceShareRole | null) => void)?.( + value as WorkspaceShareRole + ); + } else { + (onChangeShareLevel as (level: ShareRole | null) => void)?.(value as ShareRole); + } } }); @@ -122,17 +132,17 @@ AccessDropdown.displayName = 'AccessDropdown'; const metricItems: DropdownItem[] = [ { - value: 'fullAccess', + value: 'full_access', label: 'Full access', secondaryLabel: 'Can edit and share with others.' }, { - value: 'canEdit', + value: 'can_edit', label: 'Can edit', secondaryLabel: 'Can edit but not share with others.' }, { - value: 'canView', + value: 'can_view', label: 'Can view', secondaryLabel: 'Can view asset but not edit.' } @@ -140,17 +150,17 @@ const metricItems: DropdownItem[] = [ const dashboardItems: DropdownItem[] = [ { - value: 'fullAccess', + value: 'full_access', label: 'Full access', secondaryLabel: 'Can edit and share with others.' }, { - value: 'canEdit', + value: 'can_edit', label: 'Can edit', secondaryLabel: 'Can edit but not share with others.' }, { - value: 'canView', + value: 'can_view', label: 'Can view', secondaryLabel: 'Can view dashboard and metrics but not edit.' } @@ -158,17 +168,17 @@ const dashboardItems: DropdownItem[] = [ const collectionItems: DropdownItem[] = [ { - value: 'fullAccess', + value: 'full_access', label: 'Full access', secondaryLabel: 'Can edit and share with others.' }, { - value: 'canEdit', + value: 'can_edit', label: 'Can edit', secondaryLabel: 'Can edit but not share with others.' }, { - value: 'canView', + value: 'can_view', label: 'Can view', secondaryLabel: 'Can view assets but not edit.' } @@ -176,17 +186,17 @@ const collectionItems: DropdownItem[] = [ const reportItems: DropdownItem[] = [ { - value: 'fullAccess', + value: 'full_access', label: 'Full access', secondaryLabel: 'Can edit and share with others.' }, { - value: 'canEdit', + value: 'can_edit', label: 'Can edit', secondaryLabel: 'Can edit but not share with others.' }, { - value: 'canView', + value: 'can_view', label: 'Can view', secondaryLabel: 'Can view asset but not edit.' } @@ -194,17 +204,17 @@ const reportItems: DropdownItem[] = [ const workspaceItems: DropdownItem[] = [ { - value: 'fullAccess', + value: 'full_access', label: 'Full access', secondaryLabel: 'Can edit and share with others.' }, { - value: 'canEdit', + value: 'can_edit', label: 'Can edit', secondaryLabel: 'Can edit, but not share with others.' }, { - value: 'canView', + value: 'can_view', label: 'Can view', secondaryLabel: 'Cannot edit or share with others.' }, diff --git a/apps/web/src/components/features/ShareMenu/ShareMenuContent.stories.tsx b/apps/web/src/components/features/ShareMenu/ShareMenuContent.stories.tsx index 192713851..87818454b 100644 --- a/apps/web/src/components/features/ShareMenu/ShareMenuContent.stories.tsx +++ b/apps/web/src/components/features/ShareMenu/ShareMenuContent.stories.tsx @@ -18,13 +18,13 @@ const mockShareConfig: ShareConfig = { individual_permissions: [ { email: 'test_with_a_long_name_like_super_long_name@test.com', - role: 'canView', + role: 'can_view', name: 'Test User', avatar_url: null }, { email: 'test2@test.com', - role: 'fullAccess', + role: 'full_access', name: 'Test User 2 with a long name like super long name', avatar_url: null } @@ -79,7 +79,7 @@ export const ViewerPermission: Story = { assetType: 'metric', shareAssetConfig: { ...mockShareConfig, - permission: 'canView' + permission: 'can_view' } } }; diff --git a/apps/web/src/components/features/ShareMenu/ShareMenuContentBody.tsx b/apps/web/src/components/features/ShareMenu/ShareMenuContentBody.tsx index 929566283..6f4c92a9e 100644 --- a/apps/web/src/components/features/ShareMenu/ShareMenuContentBody.tsx +++ b/apps/web/src/components/features/ShareMenu/ShareMenuContentBody.tsx @@ -124,7 +124,7 @@ const ShareMenuContentShare: React.FC = React.memo( const payload: Parameters[0] = { id: assetId, params: { - workspace_sharing: role + workspace_sharing: role || 'none' } }; diff --git a/apps/web/src/components/features/metrics/StatusBadgeIndicator/helpers.ts b/apps/web/src/components/features/metrics/StatusBadgeIndicator/helpers.ts index 31b5e2706..f240e2b38 100644 --- a/apps/web/src/components/features/metrics/StatusBadgeIndicator/helpers.ts +++ b/apps/web/src/components/features/metrics/StatusBadgeIndicator/helpers.ts @@ -2,12 +2,12 @@ import type { MetricListItem } from '@buster/server-shared/metrics'; import type { VerificationStatus } from '@buster/server-shared/share'; const statusRecordText: Record = { - ['verified']: 'Verified', - ['requested']: 'Requested', - ['inReview']: 'In review', - ['backlogged']: 'Backlogged', - ['notVerified']: 'Not verified', - ['notRequested']: 'Not requested' + verified: 'Verified', + requested: 'Requested', + inReview: 'In review', + backlogged: 'Backlogged', + notVerified: 'Not verified', + notRequested: 'Not requested' }; export const getTooltipText = (status: VerificationStatus) => { diff --git a/apps/web/src/lib/share.ts b/apps/web/src/lib/share.ts index 096c01a42..b9db9de9e 100644 --- a/apps/web/src/lib/share.ts +++ b/apps/web/src/lib/share.ts @@ -5,17 +5,17 @@ export const getIsOwner = (role: ShareRole | null | undefined) => { }; export const getIsEffectiveOwner = (role: ShareRole | null | undefined) => { - return role === 'fullAccess' || role === 'owner'; + return role === 'full_access' || role === 'owner'; }; export const canEdit = (role: ShareRole | null | undefined) => { - return role === 'canEdit' || role === 'fullAccess' || role === 'owner'; + return role === 'can_edit' || role === 'full_access' || role === 'owner'; }; export const canShare = (role: ShareRole | null | undefined) => { - return role === 'fullAccess' || role === 'owner'; + return role === 'full_access' || role === 'owner'; }; export const canFilter = (role: ShareRole | null | undefined) => { - return role === 'fullAccess' || role === 'owner' || role === 'canEdit'; + return role === 'full_access' || role === 'owner' || role === 'can_edit'; }; diff --git a/packages/server-shared/package.json b/packages/server-shared/package.json index eafd65a69..daf538a77 100644 --- a/packages/server-shared/package.json +++ b/packages/server-shared/package.json @@ -1,21 +1,8 @@ { "name": "@buster/server-shared", "version": "0.0.1", - "type": "module", - "module": "src/index.ts", "private": false, - "scripts": { - "prebuild": "tsx scripts/type-import-check.ts", - "build": "tsc --build", - "build:dry-run": "tsc --build", - "dev": "tsc --watch", - "lint": "biome check --write", - "typecheck": "tsc --noEmit", - "test": "vitest run", - "test:unit": "vitest run --exclude '**/*.int.test.ts' --exclude '**/*.integration.test.ts' --passWithNoTests", - "test:integration": "vitest run **/*.int.test.ts **/*.integration.test.ts", - "test:watch": "vitest" - }, + "type": "module", "exports": { ".": { "types": "./dist/index.d.ts", @@ -82,16 +69,23 @@ "default": "./dist/lib/report/index.js" } }, + "module": "src/index.ts", + "scripts": { + "prebuild": "tsx scripts/type-import-check.ts", + "build": "tsc --build", + "build:dry-run": "tsc --build", + "dev": "tsc --watch", + "lint": "biome check --write", + "test": "vitest run", + "test:integration": "vitest run **/*.int.test.ts **/*.integration.test.ts", + "test:unit": "vitest run --exclude '**/*.int.test.ts' --exclude '**/*.integration.test.ts' --passWithNoTests", + "test:watch": "vitest", + "typecheck": "tsc --noEmit" + }, "dependencies": { "@buster/database": "workspace:*", "@buster/typescript-config": "workspace:*", "@buster/vitest-config": "workspace:*", - "@platejs/autoformat": "catalog:", - "@platejs/basic-nodes": "catalog:", - "@platejs/markdown": "catalog:", - "platejs": "catalog:", - "remark-gfm": "catalog:", - "remark-math": "^6.0.0", "zod": "catalog:" }, "devDependencies": { diff --git a/packages/server-shared/src/metrics/requests.types.ts b/packages/server-shared/src/metrics/requests.types.ts index 1adb9274d..238653226 100644 --- a/packages/server-shared/src/metrics/requests.types.ts +++ b/packages/server-shared/src/metrics/requests.types.ts @@ -53,8 +53,6 @@ export const BulkUpdateMetricVerificationStatusRequestSchema = z.array( }) ); -export const ShareDeleteRequestSchema = z.array(z.string()); - export const ShareUpdateRequestSchema = z.object({ users: z .array( @@ -79,5 +77,3 @@ export type DuplicateMetricRequest = z.infer; -export type ShareDeleteRequest = z.infer; -export type ShareUpdateRequest = z.infer; diff --git a/packages/server-shared/src/reports/index.ts b/packages/server-shared/src/reports/index.ts index fa5ce16ab..1b83aaaab 100644 --- a/packages/server-shared/src/reports/index.ts +++ b/packages/server-shared/src/reports/index.ts @@ -2,3 +2,4 @@ export * from './reports.types'; export * from './requests'; export * from './responses'; export * from './reports.types'; +export * from './report-elements'; diff --git a/packages/server-shared/src/reports/reports.types.ts b/packages/server-shared/src/reports/reports.types.ts index 06957ae9e..1d5216236 100644 --- a/packages/server-shared/src/reports/reports.types.ts +++ b/packages/server-shared/src/reports/reports.types.ts @@ -1,7 +1,7 @@ import type { ReportElements } from '@buster/database'; import { z } from 'zod'; import { AssetCollectionsSchema } from '../collections/shared-asset-collections'; -import { IndividualPermissionsSchema } from '../shared-permissions'; +import { IndividualPermissionsSchema } from '../share'; import { VersionsSchema } from '../version-shared'; export const ReportListItemSchema = z.object({ diff --git a/packages/server-shared/src/reports/requests.ts b/packages/server-shared/src/reports/requests.ts index 6e83ee564..3b04e31af 100644 --- a/packages/server-shared/src/reports/requests.ts +++ b/packages/server-shared/src/reports/requests.ts @@ -1,6 +1,5 @@ import type { ReportElements } from '@buster/database'; import { z } from 'zod'; -import { WorkspaceSharingSchema } from '../shared-permissions'; import { PaginatedRequestSchema } from '../type-utilities/pagination'; export const GetReportsListRequestSchema = PaginatedRequestSchema; @@ -15,12 +14,3 @@ export const UpdateReportRequestSchema = z export type UpdateReportRequest = z.infer; export type GetReportsListRequest = z.infer; - -export const ShareUpdateRequestSchema = z.object({ - publicly_accessible: z.boolean().optional(), - public_expiry_date: z.string().optional(), - public_password: z.string().optional(), - workspace_sharing: WorkspaceSharingSchema.optional(), -}); - -export type ShareUpdateRequest = z.infer; diff --git a/packages/server-shared/src/share/assets.ts b/packages/server-shared/src/share/assets.ts new file mode 100644 index 000000000..3849b7552 --- /dev/null +++ b/packages/server-shared/src/share/assets.ts @@ -0,0 +1,17 @@ +import type { assetPermissionRoleEnum } from '@buster/database'; +import { z } from 'zod'; + +type AssetPermissionRoleBase = (typeof assetPermissionRoleEnum.enumValues)[number]; +const AssetPermissionRoleEnums: Record = + Object.freeze({ + owner: 'owner', + viewer: 'viewer', + full_access: 'full_access', + can_edit: 'can_edit', + can_filter: 'can_filter', + can_view: 'can_view', + }); + +export const AssetPermissionRoleSchema = z.enum( + Object.values(AssetPermissionRoleEnums) as [AssetPermissionRoleBase, ...AssetPermissionRoleBase[]] +); diff --git a/packages/server-shared/src/share/index.ts b/packages/server-shared/src/share/index.ts index 6332cb85b..4ce8201c2 100644 --- a/packages/server-shared/src/share/index.ts +++ b/packages/server-shared/src/share/index.ts @@ -1,3 +1,5 @@ export * from './share-interfaces.types'; export * from './verification.types'; export * from './requests'; +export * from './individual-permissions'; +export * from './assets'; diff --git a/packages/server-shared/src/share/individual-permissions.ts b/packages/server-shared/src/share/individual-permissions.ts new file mode 100644 index 000000000..87e897654 --- /dev/null +++ b/packages/server-shared/src/share/individual-permissions.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; +import { AssetPermissionRoleSchema } from './assets'; + +export const IndividualPermissionSchema = z.object({ + id: z.string(), + name: z.string().nullable(), + email: z.string(), + avatar_url: z.string().nullable(), + role: AssetPermissionRoleSchema, +}); + +export type IndividualPermission = z.infer; + +export const IndividualPermissionsSchema = z.array(IndividualPermissionSchema); + +export type IndividualPermissions = z.infer; diff --git a/packages/server-shared/src/share/requests.ts b/packages/server-shared/src/share/requests.ts index 76fea2f67..25160fbd1 100644 --- a/packages/server-shared/src/share/requests.ts +++ b/packages/server-shared/src/share/requests.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { ShareRoleSchema } from './share-interfaces.types'; +import { ShareRoleSchema, WorkspaceShareRoleSchema } from './share-interfaces.types'; export const SharePostRequestSchema = z.array( z.object({ @@ -11,3 +11,26 @@ export const SharePostRequestSchema = z.array( ); export type SharePostRequest = z.infer; + +//Used for updating share permissions for a report, collection, or metric +export const ShareUpdateRequestSchema = z.object({ + publicly_accessible: z.boolean().optional(), + public_expiry_date: z.string().optional(), + public_password: z.string().optional(), + workspace_sharing: WorkspaceShareRoleSchema.optional(), + users: z + .array( + z.object({ + email: z.string(), + role: ShareRoleSchema, + }) + ) + .optional(), +}); + +export type ShareUpdateRequest = z.infer; + +//Used for deleting share permissions for a report, collection, or metric +export const ShareDeleteRequestSchema = z.array(z.string()); + +export type ShareDeleteRequest = z.infer; diff --git a/packages/server-shared/src/share/share-interfaces.test.ts b/packages/server-shared/src/share/share-interfaces.test.ts index 11bc384ea..616fc46cf 100644 --- a/packages/server-shared/src/share/share-interfaces.test.ts +++ b/packages/server-shared/src/share/share-interfaces.test.ts @@ -8,7 +8,7 @@ import { describe('ShareRoleSchema', () => { it('should accept valid role values', () => { - const validRoles = ['owner', 'fullAccess', 'canEdit', 'canView']; + const validRoles = ['owner', 'full_access', 'can_edit', 'can_view']; for (const role of validRoles) { const result = ShareRoleSchema.safeParse(role); @@ -148,7 +148,7 @@ describe('ShareIndividualSchema', () => { }); it('should handle all valid role combinations', () => { - const validRoles = ['owner', 'fullAccess', 'canEdit', 'canView']; + const validRoles = ['owner', 'full_access', 'can_edit', 'can_view']; for (const role of validRoles) { const individual = { @@ -233,7 +233,7 @@ describe('ShareConfigSchema', () => { public_enabled_by: 'system', publicly_accessible: true, public_password: null, - permission: 'fullAccess', + permission: 'full_access', workspace_sharing: null, workspace_member_count: null, }; @@ -244,12 +244,12 @@ describe('ShareConfigSchema', () => { if (result.success) { expect(result.data.individual_permissions).toEqual([]); expect(result.data.publicly_accessible).toBe(true); - expect(result.data.permission).toBe('fullAccess'); + expect(result.data.permission).toBe('full_access'); } }); it('should validate all permission field values', () => { - const validPermissions = ['owner', 'fullAccess', 'canEdit', 'canView']; + const validPermissions = ['owner', 'full_access', 'can_edit', 'can_view']; for (const permission of validPermissions) { const config = { @@ -361,12 +361,12 @@ describe('ShareConfigSchema', () => { }, { email: 'editor@company.com', - role: 'canEdit', + role: 'can_edit', name: 'Editor User', }, { email: 'viewer@external.com', - role: 'canView', + role: 'can_view', // name is optional }, ], @@ -374,7 +374,7 @@ describe('ShareConfigSchema', () => { public_enabled_by: 'admin@company.com', publicly_accessible: true, public_password: 'complex_password_123!', - permission: 'fullAccess', + permission: 'full_access', workspace_sharing: null, workspace_member_count: null, }; @@ -385,8 +385,8 @@ describe('ShareConfigSchema', () => { if (result.success) { expect(result.data.individual_permissions).toHaveLength(3); expect(result.data.individual_permissions?.[0]?.role).toBe('owner'); - expect(result.data.individual_permissions?.[1]?.role).toBe('canEdit'); - expect(result.data.individual_permissions?.[2]?.role).toBe('canView'); + expect(result.data.individual_permissions?.[1]?.role).toBe('can_edit'); + expect(result.data.individual_permissions?.[2]?.role).toBe('can_view'); expect(result.data.individual_permissions?.[2]?.name).toBeUndefined(); } }); diff --git a/packages/server-shared/src/share/share-interfaces.types.ts b/packages/server-shared/src/share/share-interfaces.types.ts index e6e14cd79..2d6a9de2a 100644 --- a/packages/server-shared/src/share/share-interfaces.types.ts +++ b/packages/server-shared/src/share/share-interfaces.types.ts @@ -1,15 +1,37 @@ +import type { assetPermissionRoleEnum, workspaceSharingEnum } from '@buster/database'; import { z } from 'zod'; import { AssetTypeSchema } from '../assets/asset-types.types'; -export const ShareRoleSchema = z.enum([ - 'owner', //owner of the asset - 'fullAccess', //same as owner, can share with others - 'canEdit', //can edit, cannot share - 'canView', //can view asset -]); +type ShareRoleBase = (typeof assetPermissionRoleEnum.enumValues)[number]; +export const ShareRoleEnumsConversions: Record = Object.freeze({ + owner: 'owner', + full_access: 'full_access', + can_edit: 'can_edit', + can_view: 'can_view', + viewer: 'viewer', + can_filter: 'can_filter', +}); -export const WorkspaceShareRoleSchema = z.enum([...ShareRoleSchema.options, 'none']); +export const ShareRoleSchema = z.enum( + Object.values(ShareRoleEnumsConversions) as [ShareRoleBase, ...ShareRoleBase[]] +); +//type TeamRoleBase = (typeof teamRoleEnum.enumValues)[number] | 'none'; +type WorkspaceShareRoleBase = (typeof workspaceSharingEnum.enumValues)[number]; +const WorkspaceShareRoleEnumsConversions: Record = + Object.freeze({ + full_access: 'full_access', + can_edit: 'can_edit', + can_view: 'can_view', + none: 'none', + }); + +export const WorkspaceShareRoleSchema = z.enum( + Object.values(WorkspaceShareRoleEnumsConversions) as [ + WorkspaceShareRoleBase, + ...WorkspaceShareRoleBase[], + ] +); export const ShareAssetTypeSchema = AssetTypeSchema; export const ShareIndividualSchema = z.object({ diff --git a/packages/server-shared/src/share/verification.types.ts b/packages/server-shared/src/share/verification.types.ts index 249e7ad87..a0a01291c 100644 --- a/packages/server-shared/src/share/verification.types.ts +++ b/packages/server-shared/src/share/verification.types.ts @@ -1,12 +1,19 @@ +import type { verificationEnum } from '@buster/database'; import { z } from 'zod'; -export const VerificationStatusSchema = z.enum([ - 'notRequested', - 'requested', - 'inReview', - 'verified', - 'backlogged', - 'notVerified', -]); +type VerificationStatusBase = (typeof verificationEnum.enumValues)[number] | 'notVerified'; +const VerificationStatusEnums: Record = + Object.freeze({ + notRequested: 'notRequested', + requested: 'requested', + inReview: 'inReview', + verified: 'verified', + backlogged: 'backlogged', + notVerified: 'notVerified', + }); + +export const VerificationStatusSchema = z.enum( + Object.values(VerificationStatusEnums) as [VerificationStatusBase, ...VerificationStatusBase[]] +); export type VerificationStatus = z.infer; diff --git a/packages/server-shared/src/shared-permissions/index.ts b/packages/server-shared/src/shared-permissions/index.ts deleted file mode 100644 index 590f03687..000000000 --- a/packages/server-shared/src/shared-permissions/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { assetPermissionRoleEnum, workspaceSharingEnum } from '@buster/database'; -import { z } from 'zod'; - -type AssetPermissionRoleBase = (typeof assetPermissionRoleEnum.enumValues)[number]; -const AssetPermissionRoleEnums: Record = - Object.freeze({ - owner: 'owner', - viewer: 'viewer', - full_access: 'full_access', - can_edit: 'can_edit', - can_filter: 'can_filter', - can_view: 'can_view', - }); - -export const AssetPermissionRoleSchema = z.enum( - Object.values(AssetPermissionRoleEnums) as [AssetPermissionRoleBase, ...AssetPermissionRoleBase[]] -); - -export const IndividualPermissionSchema = z.object({ - id: z.string(), - name: z.string().nullable(), - email: z.string(), - avatar_url: z.string().nullable(), - role: AssetPermissionRoleSchema, -}); - -export type IndividualPermission = z.infer; - -export const IndividualPermissionsSchema = z.array(IndividualPermissionSchema); - -export type IndividualPermissions = z.infer; - -type WorkspaceSharingBase = (typeof workspaceSharingEnum.enumValues)[number]; -const WorkspaceSharingEnums: Record = Object.freeze({ - none: 'none', - can_view: 'can_view', - can_edit: 'can_edit', - full_access: 'full_access', -}); - -export const WorkspaceSharingSchema = z.enum( - Object.values(WorkspaceSharingEnums) as [WorkspaceSharingBase, ...WorkspaceSharingBase[]] -); diff --git a/packages/server-utils/.gitignore b/packages/server-utils/.gitignore new file mode 100644 index 000000000..f36a5da1b --- /dev/null +++ b/packages/server-utils/.gitignore @@ -0,0 +1,38 @@ +# TypeScript build artifacts +dist/ +build/ +*.tsbuildinfo + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Coverage +coverage/ +*.lcov +.nyc_output + +# Node modules +node_modules/ + +# Temporary files +*.tmp +*.temp +.DS_Store + +# Environment files +.env.local +.env.*.local + +# Test artifacts +junit.xml +test-results/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo diff --git a/packages/server-utils/README.md b/packages/server-utils/README.md new file mode 100644 index 000000000..705a4c1f1 --- /dev/null +++ b/packages/server-utils/README.md @@ -0,0 +1,7 @@ +# @buster/server-utils + +Server utilities for Buster that can be used in any server-side application across the monorepo. + +## Overview + +This package provides common server-side utilities and helper functions that are shared across different backend applications in the Buster ecosystem. It centralizes reusable server logic to avoid code duplication and ensure consistency across services. diff --git a/packages/server-utils/biome.json b/packages/server-utils/biome.json new file mode 100644 index 000000000..e0ac3c56f --- /dev/null +++ b/packages/server-utils/biome.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "extends": ["../../biome.json"], + "files": { + "include": ["src/**/*", "scripts/**/*"] + } +} diff --git a/packages/server-utils/env.d.ts b/packages/server-utils/env.d.ts new file mode 100644 index 000000000..fceadd51e --- /dev/null +++ b/packages/server-utils/env.d.ts @@ -0,0 +1,10 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + NODE_ENV?: 'development' | 'production' | 'test'; + // Add your environment variables here + } + } +} + +export {}; diff --git a/packages/server-utils/package.json b/packages/server-utils/package.json new file mode 100644 index 000000000..16b63bc72 --- /dev/null +++ b/packages/server-utils/package.json @@ -0,0 +1,40 @@ +{ + "name": "@buster/server-utils", + "description": "Server utilities for Buster that can be used in any server-side application", + "version": "1.0.0", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + "./report": { + "types": "./dist/report/index.d.ts", + "default": "./dist/report/index.js" + } + }, + "scripts": { + "prebuild": "[ \"$SKIP_ENV_CHECK\" = \"true\" ] || tsx scripts/validate-env.ts", + "build": "tsc", + "build:dry-run": "tsc", + "typecheck": "tsc --noEmit", + "dev": "tsc --watch", + "lint": "biome check --write", + "test": "vitest run", + "test:unit": "vitest run --exclude '**/*.int.test.ts' --exclude '**/*.integration.test.ts' --passWithNoTests", + "test:integration": "vitest run **/*.int.test.ts **/*.integration.test.ts", + "test:watch": "vitest watch", + "test:coverage": "vitest run --coverage" + }, + "dependencies": { + "@buster/typescript-config": "workspace:*", + "@buster/vitest-config": "workspace:*", + "@buster/env-utils": "workspace:*", + "@buster/database": "workspace:*", + "zod": "catalog:", + "@platejs/autoformat": "catalog:", + "@platejs/basic-nodes": "catalog:", + "@platejs/markdown": "catalog:", + "platejs": "catalog:", + "remark-gfm": "catalog:", + "remark-math": "^6.0.0" + } +} diff --git a/packages/server-utils/scripts/validate-env.ts b/packages/server-utils/scripts/validate-env.ts new file mode 100644 index 000000000..a1accf40f --- /dev/null +++ b/packages/server-utils/scripts/validate-env.ts @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +// This script uses the shared env-utils to validate environment variables +import { loadRootEnv, validateEnv } from '@buster/env-utils'; + +// Load environment variables from root .env file +loadRootEnv(); + +// Define required environment variables for this package +const requiredEnv = { + // NODE_ENV is optional - will default to 'development' if not set + // Add your required environment variables here: + // DATABASE_URL: process.env.DATABASE_URL, + // API_KEY: process.env.API_KEY, +}; + +// Validate environment variables +const { hasErrors } = validateEnv(requiredEnv); + +if (hasErrors) { + process.exit(1); +} diff --git a/packages/server-shared/src/lib/report/MarkdownPlugin.ts b/packages/server-utils/src/report/MarkdownPlugin.ts similarity index 100% rename from packages/server-shared/src/lib/report/MarkdownPlugin.ts rename to packages/server-utils/src/report/MarkdownPlugin.ts diff --git a/packages/server-shared/src/lib/report/callout-serializer.ts b/packages/server-utils/src/report/callout-serializer.ts similarity index 100% rename from packages/server-shared/src/lib/report/callout-serializer.ts rename to packages/server-utils/src/report/callout-serializer.ts diff --git a/packages/server-shared/src/lib/report/index.ts b/packages/server-utils/src/report/index.ts similarity index 100% rename from packages/server-shared/src/lib/report/index.ts rename to packages/server-utils/src/report/index.ts diff --git a/packages/server-shared/src/lib/report/markdown-to-platejs.test.ts b/packages/server-utils/src/report/markdown-to-platejs.test.ts similarity index 100% rename from packages/server-shared/src/lib/report/markdown-to-platejs.test.ts rename to packages/server-utils/src/report/markdown-to-platejs.test.ts diff --git a/packages/server-shared/src/lib/report/markdown-to-platejs.ts b/packages/server-utils/src/report/markdown-to-platejs.ts similarity index 100% rename from packages/server-shared/src/lib/report/markdown-to-platejs.ts rename to packages/server-utils/src/report/markdown-to-platejs.ts diff --git a/packages/server-utils/tsconfig.json b/packages/server-utils/tsconfig.json new file mode 100644 index 000000000..98d2d738b --- /dev/null +++ b/packages/server-utils/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@buster/typescript-config/base.json", + "compilerOptions": { + "tsBuildInfoFile": "dist/.cache/tsbuildinfo.json", + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*", "env.d.ts"], + "exclude": ["node_modules", "dist", "tests", "**/*.test.ts", "**/*.spec.ts"] +} diff --git a/packages/server-utils/vitest.config.ts b/packages/server-utils/vitest.config.ts new file mode 100644 index 000000000..d86b4007a --- /dev/null +++ b/packages/server-utils/vitest.config.ts @@ -0,0 +1,3 @@ +import { baseConfig } from '@buster/vitest-config'; + +export default baseConfig; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb33fd8a9..5a0d50071 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1043,6 +1043,31 @@ importers: '@buster/vitest-config': specifier: workspace:* version: link:../vitest-config + zod: + specifier: 'catalog:' + version: 3.25.1 + devDependencies: + tsx: + specifier: 'catalog:' + version: 4.20.3 + vitest: + specifier: 'catalog:' + version: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@24.0.10)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(msw@2.10.4(@types/node@24.0.10)(typescript@5.8.3))(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) + + packages/server-utils: + dependencies: + '@buster/database': + specifier: workspace:* + version: link:../database + '@buster/env-utils': + specifier: workspace:* + version: link:../env-utils + '@buster/typescript-config': + specifier: workspace:* + version: link:../typescript-config + '@buster/vitest-config': + specifier: workspace:* + version: link:../vitest-config '@platejs/autoformat': specifier: 'catalog:' version: 49.0.0(platejs@49.2.4(@types/react@18.3.23)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.23.2)(slate-dom@0.116.0(slate@0.117.0))(slate@0.117.0)(use-sync-external-store@1.5.0(react@18.3.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1064,13 +1089,6 @@ importers: zod: specifier: 'catalog:' version: 3.25.1 - devDependencies: - tsx: - specifier: 'catalog:' - version: 4.20.3 - vitest: - specifier: 'catalog:' - version: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@24.0.10)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(msw@2.10.4(@types/node@24.0.10)(typescript@5.8.3))(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) packages/slack: dependencies: @@ -6783,7 +6801,6 @@ packages: bun@1.2.18: resolution: {integrity: sha512-OR+EpNckoJN4tHMVZPaTPxDj2RgpJgJwLruTIFYbO3bQMguLd0YrmkWKYqsiihcLgm2ehIjF/H1RLfZiRa7+qQ==} - cpu: [arm64, x64, aarch64] os: [darwin, linux, win32] hasBin: true @@ -19222,14 +19239,14 @@ snapshots: msw: 2.10.4(@types/node@20.19.4)(typescript@5.8.3) vite: 6.3.5(@types/node@20.19.4)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) - '@vitest/mocker@3.2.4(msw@2.10.4(@types/node@24.0.10)(typescript@5.8.3))(vite@6.3.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))': + '@vitest/mocker@3.2.4(msw@2.10.4(@types/node@24.0.10)(typescript@5.8.3))(vite@6.3.5(@types/node@20.19.4)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: msw: 2.10.4(@types/node@24.0.10)(typescript@5.8.3) - vite: 6.3.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) + vite: 6.3.5(@types/node@20.19.4)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0) '@vitest/pretty-format@2.0.5': dependencies: @@ -26558,7 +26575,7 @@ snapshots: dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.10.4(@types/node@24.0.10)(typescript@5.8.3))(vite@6.3.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/mocker': 3.2.4(msw@2.10.4(@types/node@24.0.10)(typescript@5.8.3))(vite@6.3.5(@types/node@20.19.4)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4