move enum types

This commit is contained in:
Nate Kelley 2025-08-04 18:04:32 -06:00
parent 87ea87e963
commit ffeee17365
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
39 changed files with 345 additions and 170 deletions

View File

@ -19,7 +19,8 @@
{ "path": "../packages/vitest-config" }, { "path": "../packages/vitest-config" },
{ "path": "../packages/web-tools" }, { "path": "../packages/web-tools" },
{ "path": "../packages/sandbox" }, { "path": "../packages/sandbox" },
{ "path": "../packages/env-utils" } { "path": "../packages/env-utils" },
{ "path": "../packages/server-utils" }
], ],
"settings": { "settings": {
"editor.defaultFormatter": "biomejs.biome", "editor.defaultFormatter": "biomejs.biome",

View File

@ -742,7 +742,7 @@ impl FromSql<sql_types::MessageFeedbackEnum, Pg> for MessageFeedback {
Serialize, Serialize,
)] )]
#[diesel(sql_type = sql_types::WorkspaceSharingEnum)] #[diesel(sql_type = sql_types::WorkspaceSharingEnum)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "snake_case")]
pub enum WorkspaceSharing { pub enum WorkspaceSharing {
#[serde(alias = "none")] #[serde(alias = "none")]
None, None,

View File

@ -1,10 +1,6 @@
import { getUserOrganizationId, updateReport } from '@buster/database'; import { getUserOrganizationId, updateReport } from '@buster/database';
import { import type { ShareUpdateResponse, UpdateReportResponse } from '@buster/server-shared/reports';
type ShareUpdateRequest, import { type ShareUpdateRequest, ShareUpdateRequestSchema } from '@buster/server-shared/share';
ShareUpdateRequestSchema,
type ShareUpdateResponse,
type UpdateReportResponse,
} from '@buster/server-shared/reports';
import { zValidator } from '@hono/zod-validator'; import { zValidator } from '@hono/zod-validator';
import { Hono } from 'hono'; import { Hono } from 'hono';
import { HTTPException } from 'hono/http-exception'; import { HTTPException } from 'hono/http-exception';

View File

@ -1,11 +1,8 @@
import type { ShareAssetType } from '@buster/server-shared/share'; import type { ShareAssetType } from '@buster/server-shared/share';
import type { BusterCollection, BusterCollectionListItem } from '@/api/asset_interfaces/collection'; 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 mainApi from '@/api/buster_rest/instances';
import { SharePostRequest } from '@buster/server-shared/share'; import { SharePostRequest } from '@buster/server-shared/share';
import type { ShareDeleteRequest, ShareUpdateRequest } from '@buster/server-shared/share';
export const collectionsGetList = async (params: { export const collectionsGetList = async (params: {
/** Current page number (1-based indexing) */ /** Current page number (1-based indexing) */

View File

@ -3,10 +3,7 @@ import type {
BusterDashboardResponse, BusterDashboardResponse,
DashboardConfig DashboardConfig
} from '@/api/asset_interfaces/dashboard'; } from '@/api/asset_interfaces/dashboard';
import type { import type { ShareDeleteRequest, ShareUpdateRequest } from '@buster/server-shared/share';
ShareDeleteRequest,
ShareUpdateRequest
} from '@/api/asset_interfaces/shared_interfaces';
import mainApi from '@/api/buster_rest/instances'; import mainApi from '@/api/buster_rest/instances';
import { serverFetch } from '@/api/createServerInstance'; import { serverFetch } from '@/api/createServerInstance';
import { SharePostRequest } from '@buster/server-shared/share'; import { SharePostRequest } from '@buster/server-shared/share';

View File

@ -1,8 +1,6 @@
import type { import type {
BulkUpdateMetricVerificationStatusRequest, BulkUpdateMetricVerificationStatusRequest,
BulkUpdateMetricVerificationStatusResponse, BulkUpdateMetricVerificationStatusResponse,
ShareDeleteRequest,
ShareUpdateRequest,
DeleteMetricRequest, DeleteMetricRequest,
DeleteMetricResponse, DeleteMetricResponse,
DuplicateMetricRequest, DuplicateMetricRequest,
@ -18,6 +16,7 @@ import type {
UpdateMetricResponse, UpdateMetricResponse,
ShareUpdateResponse ShareUpdateResponse
} from '@buster/server-shared/metrics'; } from '@buster/server-shared/metrics';
import type { ShareDeleteRequest, ShareUpdateRequest } from '@buster/server-shared/share';
import { serverFetch } from '@/api/createServerInstance'; import { serverFetch } from '@/api/createServerInstance';
import { mainApi } from '../instances'; import { mainApi } from '../instances';
import { SharePostRequest } from '@buster/server-shared/share'; import { SharePostRequest } from '@buster/server-shared/share';

View File

@ -10,7 +10,6 @@ import { useMemoizedFn } from '@/hooks';
import { queryKeys } from '@/api/query_keys'; import { queryKeys } from '@/api/query_keys';
import type { RustApiError } from '../errors'; import type { RustApiError } from '../errors';
import type { import type {
GetReportsListResponse,
GetReportIndividualResponse, GetReportIndividualResponse,
UpdateReportRequest, UpdateReportRequest,
UpdateReportResponse UpdateReportResponse
@ -129,9 +128,6 @@ export const useUpdateReport = () => {
queryKeys.reportsGetReport(reportId).queryKey, queryKeys.reportsGetReport(reportId).queryKey,
create(previousReport, (draft) => { create(previousReport, (draft) => {
if (data.name !== undefined) draft.name = data.name; 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; if (data.content !== undefined) draft.content = data.content;
}) })
); );

View File

@ -64,16 +64,20 @@ export const AccessDropdown: React.FC<AccessDropdownProps> = React.memo(
// Using a type-safe switch to handle all ShareRole values // Using a type-safe switch to handle all ShareRole values
switch (value) { switch (value) {
case 'fullAccess': case 'full_access':
return 'Full access'; return 'Full access';
case 'canEdit': case 'can_edit':
return 'Can edit'; return 'Can edit';
case 'canView': case 'can_view':
return 'Can view'; return 'Can view';
case 'owner': case 'owner':
return 'Owner'; return 'Owner';
case 'remove': case 'remove':
return 'Remove'; return 'Remove';
case 'viewer':
return 'Viewer';
case 'can_filter':
return 'Can filter';
case 'none': case 'none':
return 'Not shared'; return 'Not shared';
default: default:
@ -86,7 +90,13 @@ export const AccessDropdown: React.FC<AccessDropdownProps> = React.memo(
if (value === 'remove' || value === 'notShared') { if (value === 'remove' || value === 'notShared') {
onChangeShareLevel?.(null); onChangeShareLevel?.(null);
} else { } 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<ShareRole>[] = [ const metricItems: DropdownItem<ShareRole>[] = [
{ {
value: 'fullAccess', value: 'full_access',
label: 'Full access', label: 'Full access',
secondaryLabel: 'Can edit and share with others.' secondaryLabel: 'Can edit and share with others.'
}, },
{ {
value: 'canEdit', value: 'can_edit',
label: 'Can edit', label: 'Can edit',
secondaryLabel: 'Can edit but not share with others.' secondaryLabel: 'Can edit but not share with others.'
}, },
{ {
value: 'canView', value: 'can_view',
label: 'Can view', label: 'Can view',
secondaryLabel: 'Can view asset but not edit.' secondaryLabel: 'Can view asset but not edit.'
} }
@ -140,17 +150,17 @@ const metricItems: DropdownItem<ShareRole>[] = [
const dashboardItems: DropdownItem<ShareRole>[] = [ const dashboardItems: DropdownItem<ShareRole>[] = [
{ {
value: 'fullAccess', value: 'full_access',
label: 'Full access', label: 'Full access',
secondaryLabel: 'Can edit and share with others.' secondaryLabel: 'Can edit and share with others.'
}, },
{ {
value: 'canEdit', value: 'can_edit',
label: 'Can edit', label: 'Can edit',
secondaryLabel: 'Can edit but not share with others.' secondaryLabel: 'Can edit but not share with others.'
}, },
{ {
value: 'canView', value: 'can_view',
label: 'Can view', label: 'Can view',
secondaryLabel: 'Can view dashboard and metrics but not edit.' secondaryLabel: 'Can view dashboard and metrics but not edit.'
} }
@ -158,17 +168,17 @@ const dashboardItems: DropdownItem<ShareRole>[] = [
const collectionItems: DropdownItem<ShareRole>[] = [ const collectionItems: DropdownItem<ShareRole>[] = [
{ {
value: 'fullAccess', value: 'full_access',
label: 'Full access', label: 'Full access',
secondaryLabel: 'Can edit and share with others.' secondaryLabel: 'Can edit and share with others.'
}, },
{ {
value: 'canEdit', value: 'can_edit',
label: 'Can edit', label: 'Can edit',
secondaryLabel: 'Can edit but not share with others.' secondaryLabel: 'Can edit but not share with others.'
}, },
{ {
value: 'canView', value: 'can_view',
label: 'Can view', label: 'Can view',
secondaryLabel: 'Can view assets but not edit.' secondaryLabel: 'Can view assets but not edit.'
} }
@ -176,17 +186,17 @@ const collectionItems: DropdownItem<ShareRole>[] = [
const reportItems: DropdownItem<ShareRole>[] = [ const reportItems: DropdownItem<ShareRole>[] = [
{ {
value: 'fullAccess', value: 'full_access',
label: 'Full access', label: 'Full access',
secondaryLabel: 'Can edit and share with others.' secondaryLabel: 'Can edit and share with others.'
}, },
{ {
value: 'canEdit', value: 'can_edit',
label: 'Can edit', label: 'Can edit',
secondaryLabel: 'Can edit but not share with others.' secondaryLabel: 'Can edit but not share with others.'
}, },
{ {
value: 'canView', value: 'can_view',
label: 'Can view', label: 'Can view',
secondaryLabel: 'Can view asset but not edit.' secondaryLabel: 'Can view asset but not edit.'
} }
@ -194,17 +204,17 @@ const reportItems: DropdownItem<ShareRole>[] = [
const workspaceItems: DropdownItem<WorkspaceShareRole>[] = [ const workspaceItems: DropdownItem<WorkspaceShareRole>[] = [
{ {
value: 'fullAccess', value: 'full_access',
label: 'Full access', label: 'Full access',
secondaryLabel: 'Can edit and share with others.' secondaryLabel: 'Can edit and share with others.'
}, },
{ {
value: 'canEdit', value: 'can_edit',
label: 'Can edit', label: 'Can edit',
secondaryLabel: 'Can edit, but not share with others.' secondaryLabel: 'Can edit, but not share with others.'
}, },
{ {
value: 'canView', value: 'can_view',
label: 'Can view', label: 'Can view',
secondaryLabel: 'Cannot edit or share with others.' secondaryLabel: 'Cannot edit or share with others.'
}, },

View File

@ -18,13 +18,13 @@ const mockShareConfig: ShareConfig = {
individual_permissions: [ individual_permissions: [
{ {
email: 'test_with_a_long_name_like_super_long_name@test.com', email: 'test_with_a_long_name_like_super_long_name@test.com',
role: 'canView', role: 'can_view',
name: 'Test User', name: 'Test User',
avatar_url: null avatar_url: null
}, },
{ {
email: 'test2@test.com', email: 'test2@test.com',
role: 'fullAccess', role: 'full_access',
name: 'Test User 2 with a long name like super long name', name: 'Test User 2 with a long name like super long name',
avatar_url: null avatar_url: null
} }
@ -79,7 +79,7 @@ export const ViewerPermission: Story = {
assetType: 'metric', assetType: 'metric',
shareAssetConfig: { shareAssetConfig: {
...mockShareConfig, ...mockShareConfig,
permission: 'canView' permission: 'can_view'
} }
} }
}; };

View File

@ -124,7 +124,7 @@ const ShareMenuContentShare: React.FC<ShareMenuContentBodyProps> = React.memo(
const payload: Parameters<typeof onUpdateMetricShare>[0] = { const payload: Parameters<typeof onUpdateMetricShare>[0] = {
id: assetId, id: assetId,
params: { params: {
workspace_sharing: role workspace_sharing: role || 'none'
} }
}; };

View File

@ -2,12 +2,12 @@ import type { MetricListItem } from '@buster/server-shared/metrics';
import type { VerificationStatus } from '@buster/server-shared/share'; import type { VerificationStatus } from '@buster/server-shared/share';
const statusRecordText: Record<VerificationStatus, string> = { const statusRecordText: Record<VerificationStatus, string> = {
['verified']: 'Verified', verified: 'Verified',
['requested']: 'Requested', requested: 'Requested',
['inReview']: 'In review', inReview: 'In review',
['backlogged']: 'Backlogged', backlogged: 'Backlogged',
['notVerified']: 'Not verified', notVerified: 'Not verified',
['notRequested']: 'Not requested' notRequested: 'Not requested'
}; };
export const getTooltipText = (status: VerificationStatus) => { export const getTooltipText = (status: VerificationStatus) => {

View File

@ -5,17 +5,17 @@ export const getIsOwner = (role: ShareRole | null | undefined) => {
}; };
export const getIsEffectiveOwner = (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) => { 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) => { 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) => { export const canFilter = (role: ShareRole | null | undefined) => {
return role === 'fullAccess' || role === 'owner' || role === 'canEdit'; return role === 'full_access' || role === 'owner' || role === 'can_edit';
}; };

View File

@ -1,21 +1,8 @@
{ {
"name": "@buster/server-shared", "name": "@buster/server-shared",
"version": "0.0.1", "version": "0.0.1",
"type": "module",
"module": "src/index.ts",
"private": false, "private": false,
"scripts": { "type": "module",
"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"
},
"exports": { "exports": {
".": { ".": {
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@ -82,16 +69,23 @@
"default": "./dist/lib/report/index.js" "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": { "dependencies": {
"@buster/database": "workspace:*", "@buster/database": "workspace:*",
"@buster/typescript-config": "workspace:*", "@buster/typescript-config": "workspace:*",
"@buster/vitest-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:" "zod": "catalog:"
}, },
"devDependencies": { "devDependencies": {

View File

@ -53,8 +53,6 @@ export const BulkUpdateMetricVerificationStatusRequestSchema = z.array(
}) })
); );
export const ShareDeleteRequestSchema = z.array(z.string());
export const ShareUpdateRequestSchema = z.object({ export const ShareUpdateRequestSchema = z.object({
users: z users: z
.array( .array(
@ -79,5 +77,3 @@ export type DuplicateMetricRequest = z.infer<typeof DuplicateMetricRequestSchema
export type BulkUpdateMetricVerificationStatusRequest = z.infer< export type BulkUpdateMetricVerificationStatusRequest = z.infer<
typeof BulkUpdateMetricVerificationStatusRequestSchema typeof BulkUpdateMetricVerificationStatusRequestSchema
>; >;
export type ShareDeleteRequest = z.infer<typeof ShareDeleteRequestSchema>;
export type ShareUpdateRequest = z.infer<typeof ShareUpdateRequestSchema>;

View File

@ -2,3 +2,4 @@ export * from './reports.types';
export * from './requests'; export * from './requests';
export * from './responses'; export * from './responses';
export * from './reports.types'; export * from './reports.types';
export * from './report-elements';

View File

@ -1,7 +1,7 @@
import type { ReportElements } from '@buster/database'; import type { ReportElements } from '@buster/database';
import { z } from 'zod'; import { z } from 'zod';
import { AssetCollectionsSchema } from '../collections/shared-asset-collections'; import { AssetCollectionsSchema } from '../collections/shared-asset-collections';
import { IndividualPermissionsSchema } from '../shared-permissions'; import { IndividualPermissionsSchema } from '../share';
import { VersionsSchema } from '../version-shared'; import { VersionsSchema } from '../version-shared';
export const ReportListItemSchema = z.object({ export const ReportListItemSchema = z.object({

View File

@ -1,6 +1,5 @@
import type { ReportElements } from '@buster/database'; import type { ReportElements } from '@buster/database';
import { z } from 'zod'; import { z } from 'zod';
import { WorkspaceSharingSchema } from '../shared-permissions';
import { PaginatedRequestSchema } from '../type-utilities/pagination'; import { PaginatedRequestSchema } from '../type-utilities/pagination';
export const GetReportsListRequestSchema = PaginatedRequestSchema; export const GetReportsListRequestSchema = PaginatedRequestSchema;
@ -15,12 +14,3 @@ export const UpdateReportRequestSchema = z
export type UpdateReportRequest = z.infer<typeof UpdateReportRequestSchema>; export type UpdateReportRequest = z.infer<typeof UpdateReportRequestSchema>;
export type GetReportsListRequest = z.infer<typeof GetReportsListRequestSchema>; export type GetReportsListRequest = z.infer<typeof GetReportsListRequestSchema>;
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<typeof ShareUpdateRequestSchema>;

View File

@ -0,0 +1,17 @@
import type { assetPermissionRoleEnum } from '@buster/database';
import { z } from 'zod';
type AssetPermissionRoleBase = (typeof assetPermissionRoleEnum.enumValues)[number];
const AssetPermissionRoleEnums: Record<AssetPermissionRoleBase, AssetPermissionRoleBase> =
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[]]
);

View File

@ -1,3 +1,5 @@
export * from './share-interfaces.types'; export * from './share-interfaces.types';
export * from './verification.types'; export * from './verification.types';
export * from './requests'; export * from './requests';
export * from './individual-permissions';
export * from './assets';

View File

@ -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<typeof IndividualPermissionSchema>;
export const IndividualPermissionsSchema = z.array(IndividualPermissionSchema);
export type IndividualPermissions = z.infer<typeof IndividualPermissionsSchema>;

View File

@ -1,5 +1,5 @@
import { z } from 'zod'; import { z } from 'zod';
import { ShareRoleSchema } from './share-interfaces.types'; import { ShareRoleSchema, WorkspaceShareRoleSchema } from './share-interfaces.types';
export const SharePostRequestSchema = z.array( export const SharePostRequestSchema = z.array(
z.object({ z.object({
@ -11,3 +11,26 @@ export const SharePostRequestSchema = z.array(
); );
export type SharePostRequest = z.infer<typeof SharePostRequestSchema>; export type SharePostRequest = z.infer<typeof SharePostRequestSchema>;
//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<typeof ShareUpdateRequestSchema>;
//Used for deleting share permissions for a report, collection, or metric
export const ShareDeleteRequestSchema = z.array(z.string());
export type ShareDeleteRequest = z.infer<typeof ShareDeleteRequestSchema>;

View File

@ -8,7 +8,7 @@ import {
describe('ShareRoleSchema', () => { describe('ShareRoleSchema', () => {
it('should accept valid role values', () => { 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) { for (const role of validRoles) {
const result = ShareRoleSchema.safeParse(role); const result = ShareRoleSchema.safeParse(role);
@ -148,7 +148,7 @@ describe('ShareIndividualSchema', () => {
}); });
it('should handle all valid role combinations', () => { 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) { for (const role of validRoles) {
const individual = { const individual = {
@ -233,7 +233,7 @@ describe('ShareConfigSchema', () => {
public_enabled_by: 'system', public_enabled_by: 'system',
publicly_accessible: true, publicly_accessible: true,
public_password: null, public_password: null,
permission: 'fullAccess', permission: 'full_access',
workspace_sharing: null, workspace_sharing: null,
workspace_member_count: null, workspace_member_count: null,
}; };
@ -244,12 +244,12 @@ describe('ShareConfigSchema', () => {
if (result.success) { if (result.success) {
expect(result.data.individual_permissions).toEqual([]); expect(result.data.individual_permissions).toEqual([]);
expect(result.data.publicly_accessible).toBe(true); 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', () => { 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) { for (const permission of validPermissions) {
const config = { const config = {
@ -361,12 +361,12 @@ describe('ShareConfigSchema', () => {
}, },
{ {
email: 'editor@company.com', email: 'editor@company.com',
role: 'canEdit', role: 'can_edit',
name: 'Editor User', name: 'Editor User',
}, },
{ {
email: 'viewer@external.com', email: 'viewer@external.com',
role: 'canView', role: 'can_view',
// name is optional // name is optional
}, },
], ],
@ -374,7 +374,7 @@ describe('ShareConfigSchema', () => {
public_enabled_by: 'admin@company.com', public_enabled_by: 'admin@company.com',
publicly_accessible: true, publicly_accessible: true,
public_password: 'complex_password_123!', public_password: 'complex_password_123!',
permission: 'fullAccess', permission: 'full_access',
workspace_sharing: null, workspace_sharing: null,
workspace_member_count: null, workspace_member_count: null,
}; };
@ -385,8 +385,8 @@ describe('ShareConfigSchema', () => {
if (result.success) { if (result.success) {
expect(result.data.individual_permissions).toHaveLength(3); expect(result.data.individual_permissions).toHaveLength(3);
expect(result.data.individual_permissions?.[0]?.role).toBe('owner'); expect(result.data.individual_permissions?.[0]?.role).toBe('owner');
expect(result.data.individual_permissions?.[1]?.role).toBe('canEdit'); expect(result.data.individual_permissions?.[1]?.role).toBe('can_edit');
expect(result.data.individual_permissions?.[2]?.role).toBe('canView'); expect(result.data.individual_permissions?.[2]?.role).toBe('can_view');
expect(result.data.individual_permissions?.[2]?.name).toBeUndefined(); expect(result.data.individual_permissions?.[2]?.name).toBeUndefined();
} }
}); });

View File

@ -1,15 +1,37 @@
import type { assetPermissionRoleEnum, workspaceSharingEnum } from '@buster/database';
import { z } from 'zod'; import { z } from 'zod';
import { AssetTypeSchema } from '../assets/asset-types.types'; import { AssetTypeSchema } from '../assets/asset-types.types';
export const ShareRoleSchema = z.enum([ type ShareRoleBase = (typeof assetPermissionRoleEnum.enumValues)[number];
'owner', //owner of the asset export const ShareRoleEnumsConversions: Record<ShareRoleBase, ShareRoleBase> = Object.freeze({
'fullAccess', //same as owner, can share with others owner: 'owner',
'canEdit', //can edit, cannot share full_access: 'full_access',
'canView', //can view asset 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<WorkspaceShareRoleBase, WorkspaceShareRoleBase> =
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 ShareAssetTypeSchema = AssetTypeSchema;
export const ShareIndividualSchema = z.object({ export const ShareIndividualSchema = z.object({

View File

@ -1,12 +1,19 @@
import type { verificationEnum } from '@buster/database';
import { z } from 'zod'; import { z } from 'zod';
export const VerificationStatusSchema = z.enum([ type VerificationStatusBase = (typeof verificationEnum.enumValues)[number] | 'notVerified';
'notRequested', const VerificationStatusEnums: Record<VerificationStatusBase, VerificationStatusBase> =
'requested', Object.freeze({
'inReview', notRequested: 'notRequested',
'verified', requested: 'requested',
'backlogged', inReview: 'inReview',
'notVerified', verified: 'verified',
]); backlogged: 'backlogged',
notVerified: 'notVerified',
});
export const VerificationStatusSchema = z.enum(
Object.values(VerificationStatusEnums) as [VerificationStatusBase, ...VerificationStatusBase[]]
);
export type VerificationStatus = z.infer<typeof VerificationStatusSchema>; export type VerificationStatus = z.infer<typeof VerificationStatusSchema>;

View File

@ -1,43 +0,0 @@
import type { assetPermissionRoleEnum, workspaceSharingEnum } from '@buster/database';
import { z } from 'zod';
type AssetPermissionRoleBase = (typeof assetPermissionRoleEnum.enumValues)[number];
const AssetPermissionRoleEnums: Record<AssetPermissionRoleBase, AssetPermissionRoleBase> =
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<typeof IndividualPermissionSchema>;
export const IndividualPermissionsSchema = z.array(IndividualPermissionSchema);
export type IndividualPermissions = z.infer<typeof IndividualPermissionsSchema>;
type WorkspaceSharingBase = (typeof workspaceSharingEnum.enumValues)[number];
const WorkspaceSharingEnums: Record<WorkspaceSharingBase, WorkspaceSharingBase> = 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[]]
);

38
packages/server-utils/.gitignore vendored Normal file
View File

@ -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

View File

@ -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.

View File

@ -0,0 +1,7 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"extends": ["../../biome.json"],
"files": {
"include": ["src/**/*", "scripts/**/*"]
}
}

10
packages/server-utils/env.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
NODE_ENV?: 'development' | 'production' | 'test';
// Add your environment variables here
}
}
}
export {};

View File

@ -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"
}
}

View File

@ -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);
}

View File

@ -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"]
}

View File

@ -0,0 +1,3 @@
import { baseConfig } from '@buster/vitest-config';
export default baseConfig;

View File

@ -1043,6 +1043,31 @@ importers:
'@buster/vitest-config': '@buster/vitest-config':
specifier: workspace:* specifier: workspace:*
version: link:../vitest-config 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': '@platejs/autoformat':
specifier: 'catalog:' 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) 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: zod:
specifier: 'catalog:' specifier: 'catalog:'
version: 3.25.1 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: packages/slack:
dependencies: dependencies:
@ -6783,7 +6801,6 @@ packages:
bun@1.2.18: bun@1.2.18:
resolution: {integrity: sha512-OR+EpNckoJN4tHMVZPaTPxDj2RgpJgJwLruTIFYbO3bQMguLd0YrmkWKYqsiihcLgm2ehIjF/H1RLfZiRa7+qQ==} resolution: {integrity: sha512-OR+EpNckoJN4tHMVZPaTPxDj2RgpJgJwLruTIFYbO3bQMguLd0YrmkWKYqsiihcLgm2ehIjF/H1RLfZiRa7+qQ==}
cpu: [arm64, x64, aarch64]
os: [darwin, linux, win32] os: [darwin, linux, win32]
hasBin: true hasBin: true
@ -19222,14 +19239,14 @@ snapshots:
msw: 2.10.4(@types/node@20.19.4)(typescript@5.8.3) 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) 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: dependencies:
'@vitest/spy': 3.2.4 '@vitest/spy': 3.2.4
estree-walker: 3.0.3 estree-walker: 3.0.3
magic-string: 0.30.17 magic-string: 0.30.17
optionalDependencies: optionalDependencies:
msw: 2.10.4(@types/node@24.0.10)(typescript@5.8.3) 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': '@vitest/pretty-format@2.0.5':
dependencies: dependencies:
@ -26558,7 +26575,7 @@ snapshots:
dependencies: dependencies:
'@types/chai': 5.2.2 '@types/chai': 5.2.2
'@vitest/expect': 3.2.4 '@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/pretty-format': 3.2.4
'@vitest/runner': 3.2.4 '@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4 '@vitest/snapshot': 3.2.4