diff --git a/apps/web/src/api/asset_interfaces/datasets/permissionInterfaces.ts b/apps/web/src/api/asset_interfaces/datasets/permissionInterfaces.ts index 4b82093db..191ee579d 100644 --- a/apps/web/src/api/asset_interfaces/datasets/permissionInterfaces.ts +++ b/apps/web/src/api/asset_interfaces/datasets/permissionInterfaces.ts @@ -9,6 +9,7 @@ export interface ListPermissionUsersResponse { name: string; email: string; assigned: boolean; + avatar_url: string | null; } export interface DatasetPermissionOverviewUser { @@ -16,6 +17,7 @@ export interface DatasetPermissionOverviewUser { name: string; email: string; can_query: boolean; + avatar_url: string | null; lineage: { name: string; id: string; diff --git a/apps/web/src/api/asset_interfaces/organizations/interfaces.ts b/apps/web/src/api/asset_interfaces/organizations/interfaces.ts index 7a2774591..db6d69957 100644 --- a/apps/web/src/api/asset_interfaces/organizations/interfaces.ts +++ b/apps/web/src/api/asset_interfaces/organizations/interfaces.ts @@ -1,25 +1,10 @@ -export interface BusterOrganization { - created_at: string; - id: string; - deleted_at: string | null; - domain: string; - name: string; - updated_at: string; - role: BusterOrganizationRole; -} +import type { OrganizationRole } from '@buster/server-shared/organization'; -export enum BusterOrganizationRole { - WORKSPACE_ADMIN = 'workspaceAdmin', - DATA_ADMIN = 'dataAdmin', - QUERIER = 'querier', - RESTRICTED_QUERIER = 'restrictedQuerier', - VIEWER = 'viewer' -} - -export const BusterOrganizationRoleLabels = { - [BusterOrganizationRole.WORKSPACE_ADMIN]: 'Workspace Admin', - [BusterOrganizationRole.DATA_ADMIN]: 'Data Admin', - [BusterOrganizationRole.QUERIER]: 'Querier', - [BusterOrganizationRole.RESTRICTED_QUERIER]: 'Restricted Querier', - [BusterOrganizationRole.VIEWER]: 'Viewer' +export const BusterOrganizationRoleLabels: Record = { + workspace_admin: 'Workspace Admin', + data_admin: 'Data Admin', + querier: 'Querier', + restricted_querier: 'Restricted Querier', + viewer: 'Viewer', + none: 'None' }; diff --git a/apps/web/src/api/asset_interfaces/permission/interfaces.ts b/apps/web/src/api/asset_interfaces/permission/interfaces.ts index 0c44f2d5a..f70b2481b 100644 --- a/apps/web/src/api/asset_interfaces/permission/interfaces.ts +++ b/apps/web/src/api/asset_interfaces/permission/interfaces.ts @@ -1,14 +1,14 @@ import type { BusterDatasetListItem } from '../datasets'; -import type { BusterOrganizationRole } from '../organizations'; +import type { OrganizationRole } from '@buster/server-shared/organization'; export interface BusterPermissionListUser { id: string; email: string; name: string; - role: BusterOrganizationRole; + role: OrganizationRole; belongs_to: boolean; //only shows up with no filters. To lazy to type this out better - team_role?: BusterOrganizationRole; + team_role?: OrganizationRole; //??? team_count: number; permission_group_count: number; @@ -18,7 +18,7 @@ export interface BusterPermissionUser { id: string; email: string; name: string; - role: BusterOrganizationRole; + role: OrganizationRole; created_at: string; permission_group_count: number; permission_groups: { @@ -33,7 +33,7 @@ export interface BusterPermissionUser { id: string; member_count: number; name: string; - team_role: BusterOrganizationRole; + team_role: OrganizationRole; }[]; updated_at: string; edit_sql: boolean; @@ -49,7 +49,7 @@ export interface BusterPermissionListTeam { name: string; member_count: number; permission_group_count: number; - team_role: BusterOrganizationRole; + team_role: OrganizationRole; belongs_to: boolean; } @@ -69,7 +69,7 @@ export interface BusterPermissionTeam { email: string; id: string; name: string; - role: BusterOrganizationRole; + role: OrganizationRole; }[]; permission_groups: { dataset_count: number; @@ -113,7 +113,7 @@ export interface BusterPermissionGroup { id: string; member_count: number; name: string; - team_role: BusterOrganizationRole; + team_role: OrganizationRole; }[]; updated_by: string; user_count: number; @@ -124,10 +124,10 @@ export interface BusterPermissionListUser { id: string; email: string; name: string; - role: BusterOrganizationRole; + role: OrganizationRole; belongs_to: boolean; //only shows up with no filters. To lazy to type this out better - team_role?: BusterOrganizationRole; + team_role?: OrganizationRole; //??? team_count: number; permission_group_count: number; @@ -137,7 +137,7 @@ export interface BusterPermissionUser { id: string; email: string; name: string; - role: BusterOrganizationRole; + role: OrganizationRole; created_at: string; permission_group_count: number; permission_groups: { @@ -152,7 +152,7 @@ export interface BusterPermissionUser { id: string; member_count: number; name: string; - team_role: BusterOrganizationRole; + team_role: OrganizationRole; }[]; updated_at: string; edit_sql: boolean; @@ -168,7 +168,7 @@ export interface BusterPermissionListTeam { name: string; member_count: number; permission_group_count: number; - team_role: BusterOrganizationRole; + team_role: OrganizationRole; belongs_to: boolean; } @@ -188,7 +188,7 @@ export interface BusterPermissionTeam { email: string; id: string; name: string; - role: BusterOrganizationRole; + role: OrganizationRole; }[]; permission_groups: { dataset_count: number; @@ -232,7 +232,7 @@ export interface BusterPermissionGroup { id: string; member_count: number; name: string; - team_role: BusterOrganizationRole; + team_role: OrganizationRole; }[]; updated_by: string; user_count: number; diff --git a/apps/web/src/api/asset_interfaces/permission_groups/interfaces.ts b/apps/web/src/api/asset_interfaces/permission_groups/interfaces.ts index aae11ea8d..e99e2b5e1 100644 --- a/apps/web/src/api/asset_interfaces/permission_groups/interfaces.ts +++ b/apps/web/src/api/asset_interfaces/permission_groups/interfaces.ts @@ -16,6 +16,7 @@ export interface GetPermissionGroupUsersResponse { assigned: boolean; email: string; name: string; + avatar_url: string | null; } export interface GetPermissionGroupDatasetsResponse { diff --git a/apps/web/src/api/asset_interfaces/users/interfaces.ts b/apps/web/src/api/asset_interfaces/users/interfaces.ts index c2c813892..8dd63f35b 100644 --- a/apps/web/src/api/asset_interfaces/users/interfaces.ts +++ b/apps/web/src/api/asset_interfaces/users/interfaces.ts @@ -1,76 +1,12 @@ -import type { BusterOrganization, BusterOrganizationRole } from '../organizations'; -import type { BusterPermissionUser } from '../permission'; -import type { ShareAssetType } from '@buster/server-shared/share'; - -export interface BusterUserPalette { - id: string; - palette: string[]; -} - -export enum TeamRole { - MANAGER = 'manager', - MEMBER = 'member', - NONE = 'none' -} - -export interface BusterUserTeam { - id: string; - name: string; - edit_sql: boolean; - email_slack_enabled: boolean; - export_assets: boolean; - organization_id: string; - sharing_settings: BusterPermissionUser['sharing_setting']; - upload_csv: boolean; - updated_at: string; - created_at: string; - deleted_at: string | null; - role: TeamRole; -} - -export interface BusterUserFavorite { - id: string; - asset_type: ShareAssetType; - index?: number; - name: string; -} - -export type BusterUserFavoriteAsset = { - id: string; - ype: ShareAssetType; - index?: number; - title: string; -}; - -export interface BusterUser { - config: Record; - created_at: string; - email: string; - favorites: BusterUserFavorite[]; - id: string; - name: string; - updated_at: string; -} - -export interface BusterUserResponse { - user: BusterUser; - teams: BusterUserTeam[]; - organizations: BusterOrganization[] | null; -} - -export interface BusterUserListItem { - email: string; - id: string; - name: string; - role: null; -} +import type { OrganizationRole } from '@buster/server-shared/organization'; export interface OrganizationUser { id: string; email: string; name: string; + avatar_url: string | null; status: 'active' | 'inactive'; - role: BusterOrganizationRole; + role: OrganizationRole; datasets: OrganizationUserDataset[]; } diff --git a/apps/web/src/api/asset_interfaces/users/permissionInterfaces.ts b/apps/web/src/api/asset_interfaces/users/permissionInterfaces.ts index 73a1d6daa..59ea15f93 100644 --- a/apps/web/src/api/asset_interfaces/users/permissionInterfaces.ts +++ b/apps/web/src/api/asset_interfaces/users/permissionInterfaces.ts @@ -1,4 +1,4 @@ -import type { TeamRole } from './interfaces'; +import type { TeamRole } from '@buster/server-shared/teams'; export interface BusterUserDatasetGroup { id: string; diff --git a/apps/web/src/api/buster_rest/organizations/requests.ts b/apps/web/src/api/buster_rest/organizations/requests.ts index 5409835a7..3ce8eada2 100644 --- a/apps/web/src/api/buster_rest/organizations/requests.ts +++ b/apps/web/src/api/buster_rest/organizations/requests.ts @@ -1,7 +1,7 @@ -import type { BusterOrganization } from '@/api/asset_interfaces/organizations'; import type { OrganizationUser } from '@/api/asset_interfaces/users'; import { serverFetch } from '../../createServerInstance'; import { mainApi } from '../instances'; +import type { Organization } from '@buster/server-shared/organization'; export const getOrganizationUsers = async ({ organizationId @@ -22,5 +22,5 @@ export const getOrganizationUsers_server = async ({ }; export const createOrganization = async (organization: { name: string }) => { - return mainApi.post('/organizations', organization).then((res) => res.data); + return mainApi.post('/organizations', organization).then((res) => res.data); }; diff --git a/apps/web/src/api/buster_rest/teams/requests.ts b/apps/web/src/api/buster_rest/teams/requests.ts index 3bafb6d75..8f598072f 100644 --- a/apps/web/src/api/buster_rest/teams/requests.ts +++ b/apps/web/src/api/buster_rest/teams/requests.ts @@ -1,4 +1,4 @@ -import type { BusterUserTeam } from '@/api/asset_interfaces/users'; +import type { TeamListResponse, GetTeamListRequest } from '@buster/server-shared/teams'; import { mainApi } from '../instances'; export const createTeam = async (params: { @@ -10,12 +10,6 @@ export const createTeam = async (params: { return mainApi.post<{ id: string }>('/teams', params).then((res) => res.data); }; -export const getTeamsList = async (params: { - page_size?: number; - page?: number; - permission_group_id?: string | null; - user_id?: string | null; - belongs_to?: boolean | null; -}) => { - return mainApi.get('/teams', { params }).then((res) => res.data); +export const getTeamsList = async (params: GetTeamListRequest) => { + return mainApi.get('/teams', { params }).then((res) => res.data); }; diff --git a/apps/web/src/api/buster_rest/users/permissions/requests.ts b/apps/web/src/api/buster_rest/users/permissions/requests.ts index 81cedf498..0d479b5d3 100644 --- a/apps/web/src/api/buster_rest/users/permissions/requests.ts +++ b/apps/web/src/api/buster_rest/users/permissions/requests.ts @@ -3,9 +3,9 @@ import type { BusterUserDataset, BusterUserDatasetGroup, BusterUserPermissionGroup, - BusterUserTeamListItem, - TeamRole + BusterUserTeamListItem } from '@/api/asset_interfaces/users'; +import type { TeamRole } from '@buster/server-shared/teams'; import { serverFetch } from '../../../createServerInstance'; import { mainApi } from '../../instances'; diff --git a/apps/web/src/api/buster_rest/users/requests.ts b/apps/web/src/api/buster_rest/users/requests.ts index 27575a608..89e0517a8 100644 --- a/apps/web/src/api/buster_rest/users/requests.ts +++ b/apps/web/src/api/buster_rest/users/requests.ts @@ -1,26 +1,26 @@ import type { ShareAssetType } from '@buster/server-shared/share'; -import type { - BusterUserFavorite, - BusterUserListItem, - BusterUserResponse, - OrganizationUser -} from '@/api/asset_interfaces/users'; +import type { OrganizationUser } from '@/api/asset_interfaces/users'; import { BASE_URL } from '../config'; import { serverFetch } from '../../createServerInstance'; import { mainApi } from '../instances'; +import type { + UserResponse, + UserFavoriteResponse, + UserListResponse +} from '@buster/server-shared/user'; -export const getMyUserInfo = async (): Promise => { - return mainApi.get('/users').then((response) => response.data); +export const getMyUserInfo = async () => { + return mainApi.get('/users').then((response) => response.data); }; export const getMyUserInfo_server = async ({ jwtToken }: { jwtToken: string | undefined; -}): Promise => { +}): Promise => { if (!jwtToken) { //If Anonymous user, it will fail, so we catch the error and return undefined - return await serverFetch('/users', { + return await serverFetch('/users', { method: 'GET' }); } @@ -41,7 +41,7 @@ export const getMyUserInfo_server = async ({ ...errorData }; } - return response.json(); + return (await response.json()) as UserResponse; }); }; @@ -82,11 +82,11 @@ export const inviteUser = async ({ //USER FAVORITES export const getUserFavorites = async () => { - return mainApi.get('/users/favorites').then((response) => response.data); + return mainApi.get('/users/favorites').then((response) => response.data); }; export const getUserFavorites_server = async () => { - return serverFetch('/users/favorites'); + return serverFetch('/users/favorites'); }; export const createUserFavorite = async ( @@ -98,19 +98,19 @@ export const createUserFavorite = async ( }[] ) => { return mainApi - .post('/users/favorites', payload) + .post('/users/favorites', payload) .then((response) => response.data); }; export const deleteUserFavorite = async (data: string[]) => { return mainApi - .delete('/users/favorites', { data }) + .delete('/users/favorites', { data }) .then((response) => response.data); }; export const updateUserFavorites = async (payload: string[]) => { return mainApi - .put('/users/favorites', payload) + .put('/users/favorites', payload) .then((response) => response.data); }; @@ -122,10 +122,10 @@ export const getUserList = async (payload: { page_size?: number; }) => { return mainApi - .get('/users', { params: payload }) + .get('/users', { params: payload }) .then((response) => response.data); }; export const getUserList_server = async (payload: Parameters[0]) => { - return serverFetch('/users', { params: payload }); + return serverFetch('/users', { params: payload }); }; diff --git a/apps/web/src/api/query_keys/users.ts b/apps/web/src/api/query_keys/users.ts index 6f5c8d43d..3797d8eb0 100644 --- a/apps/web/src/api/query_keys/users.ts +++ b/apps/web/src/api/query_keys/users.ts @@ -3,22 +3,24 @@ import type { BusterUserAttribute, BusterUserDataset, BusterUserDatasetGroup, - BusterUserFavorite, - BusterUserListItem, BusterUserPermissionGroup, - BusterUserResponse, BusterUserTeamListItem, OrganizationUser } from '@/api/asset_interfaces/users'; +import type { + UserFavoriteResponse, + UserResponse, + UserListResponse +} from '@buster/server-shared/user'; -const favoritesGetList = queryOptions({ +const favoritesGetList = queryOptions({ queryKey: ['myself', 'list', 'favorites'] as const, staleTime: 1000 * 60 * 60, // 1 hour, initialData: [], initialDataUpdatedAt: 0 }); -const userGetUserMyself = queryOptions({ +const userGetUserMyself = queryOptions({ queryKey: ['myself'] as const, staleTime: 1000 * 60 * 60 // 1 hour }); @@ -54,7 +56,7 @@ const userGetUserDatasetGroups = (userId: string) => }); const userGetUserList = (params: { team_id: string; page?: number; page_size?: number }) => - queryOptions({ + queryOptions({ queryKey: ['users', 'list', params] as const }); diff --git a/apps/web/src/app/app/(primary_layout)/datasets/[datasetId]/permissions/overview/_PermissionOverview/PermissionListUserContainer.tsx b/apps/web/src/app/app/(primary_layout)/datasets/[datasetId]/permissions/overview/_PermissionOverview/PermissionListUserContainer.tsx index 189a5eb3b..dbed6b1df 100644 --- a/apps/web/src/app/app/(primary_layout)/datasets/[datasetId]/permissions/overview/_PermissionOverview/PermissionListUserContainer.tsx +++ b/apps/web/src/app/app/(primary_layout)/datasets/[datasetId]/permissions/overview/_PermissionOverview/PermissionListUserContainer.tsx @@ -104,7 +104,12 @@ export const PermissionListUserContainer: React.FC<{ rows={rows} showHeader={false} showSelectAll={false} - emptyState={useMemo(() => , [])} + emptyState={useMemo( + () => ( + + ), + [] + )} /> @@ -113,7 +118,7 @@ export const PermissionListUserContainer: React.FC<{ PermissionListUserContainer.displayName = 'PermissionListUserContainer'; const UserInfoCell = React.memo(({ user }: { user: DatasetPermissionOverviewUser }) => { - return ; + return ; }); UserInfoCell.displayName = 'UserInfoCell'; diff --git a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/dataset-groups/[datasetGroupId]/users/DatasetGroupUsersListContainer.tsx b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/dataset-groups/[datasetGroupId]/users/DatasetGroupUsersListContainer.tsx index a5f3821a9..b629022ac 100644 --- a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/dataset-groups/[datasetGroupId]/users/DatasetGroupUsersListContainer.tsx +++ b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/dataset-groups/[datasetGroupId]/users/DatasetGroupUsersListContainer.tsx @@ -39,7 +39,9 @@ export const DatasetGroupUsersListContainer: React.FC<{ title: 'Name', dataIndex: 'name', render: (name, user: GetPermissionGroupUsersResponse) => { - return ; + return ( + + ); } }, { diff --git a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/permission-groups/[permissionGroupId]/users/_PermissionGroupUsersController/PermissionGroupUsersListContainer.tsx b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/permission-groups/[permissionGroupId]/users/_PermissionGroupUsersController/PermissionGroupUsersListContainer.tsx index 63000b951..70cca1ffa 100644 --- a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/permission-groups/[permissionGroupId]/users/_PermissionGroupUsersController/PermissionGroupUsersListContainer.tsx +++ b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/permission-groups/[permissionGroupId]/users/_PermissionGroupUsersController/PermissionGroupUsersListContainer.tsx @@ -34,7 +34,7 @@ export const PermissionGroupUsersListContainer: React.FC<{ title: 'Name', dataIndex: 'name', render: (name: string, user: GetPermissionGroupUsersResponse) => { - return ; + return ; } }, { diff --git a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/ListUsersComponent.tsx b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/ListUsersComponent.tsx index f5a513cb1..1dd0d0522 100644 --- a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/ListUsersComponent.tsx +++ b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/ListUsersComponent.tsx @@ -24,7 +24,7 @@ export const ListUsersComponent: React.FC<{ title: 'Name', dataIndex: 'name', render: (name: string, user: OrganizationUser) => { - return ; + return ; } }, { diff --git a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/_LayoutHeaderAndSegment/UserHeader.tsx b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/_LayoutHeaderAndSegment/UserHeader.tsx index d0d84297e..888fdbb81 100644 --- a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/_LayoutHeaderAndSegment/UserHeader.tsx +++ b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/_LayoutHeaderAndSegment/UserHeader.tsx @@ -16,7 +16,7 @@ UserHeader.displayName = 'UserHeader'; const UserInfo: React.FC<{ user: OrganizationUser }> = ({ user }) => { return (
- +
{user.name} diff --git a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/_overview/UserDefaultAccess.tsx b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/_overview/UserDefaultAccess.tsx index 00633ffb1..cea124149 100644 --- a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/_overview/UserDefaultAccess.tsx +++ b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/_overview/UserDefaultAccess.tsx @@ -1,10 +1,5 @@ import React from 'react'; -import { - BusterOrganizationRole, - BusterOrganizationRoleLabels, - type BusterUser, - type OrganizationUser -} from '@/api/asset_interfaces'; +import { BusterOrganizationRoleLabels, type OrganizationUser } from '@/api/asset_interfaces'; import { useUpdateUser } from '@/api/buster_rest/users'; import { Card, @@ -17,11 +12,12 @@ import { Select, type SelectItem } from '@/components/ui/select'; import { AppTooltip } from '@/components/ui/tooltip'; import { Text } from '@/components/ui/typography'; import { useMemoizedFn } from '@/hooks'; +import { User } from '@buster/server-shared/user'; export const UserDefaultAccess: React.FC<{ user: OrganizationUser; isAdmin: boolean; - myUser: BusterUser; + myUser: User; refetchUser: () => void; }> = ({ user, isAdmin, myUser, refetchUser }) => { const { mutateAsync } = useUpdateUser(); @@ -45,17 +41,17 @@ export const UserDefaultAccess: React.FC<{ }; const accessOptions: SelectItem[] = [ - { label: BusterOrganizationRoleLabels.dataAdmin, value: BusterOrganizationRole.DATA_ADMIN }, + { label: BusterOrganizationRoleLabels.data_admin, value: 'data_admin' }, { - label: BusterOrganizationRoleLabels.workspaceAdmin, - value: BusterOrganizationRole.WORKSPACE_ADMIN + label: BusterOrganizationRoleLabels.workspace_admin, + value: 'workspace_admin' }, - { label: BusterOrganizationRoleLabels.querier, value: BusterOrganizationRole.QUERIER }, + { label: BusterOrganizationRoleLabels.querier, value: 'querier' }, { - label: BusterOrganizationRoleLabels.restrictedQuerier, - value: BusterOrganizationRole.RESTRICTED_QUERIER + label: BusterOrganizationRoleLabels.restricted_querier, + value: 'restricted_querier' }, - { label: BusterOrganizationRoleLabels.viewer, value: BusterOrganizationRole.VIEWER } + { label: BusterOrganizationRoleLabels.viewer, value: 'viewer' } ]; const DefaultAccessCard = React.memo( diff --git a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/teams/UserTeamsListContainer.tsx b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/teams/UserTeamsListContainer.tsx index a08d84b76..8a7b42c03 100644 --- a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/teams/UserTeamsListContainer.tsx +++ b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/teams/UserTeamsListContainer.tsx @@ -2,7 +2,7 @@ import pluralize from 'pluralize'; import React, { useMemo, useState } from 'react'; -import type { BusterUserTeamListItem, TeamRole } from '@/api/asset_interfaces'; +import type { BusterUserTeamListItem } from '@/api/asset_interfaces'; import { useUpdateUserTeams } from '@/api/buster_rest/users/permissions'; import { PermissionAssignTeamRole } from '@/components/features/PermissionComponents'; import { @@ -11,6 +11,7 @@ import { EmptyStateList, InfiniteListContainer } from '@/components/ui/list'; +import type { TeamRole } from '@buster/server-shared/teams'; import { BusterInfiniteList } from '@/components/ui/list/BusterInfiniteList'; import { Text } from '@/components/ui/typography'; import { useMemoizedFn } from '@/hooks'; diff --git a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/teams/UserTeamsSelectedPopup.tsx b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/teams/UserTeamsSelectedPopup.tsx index bc9908617..87c1b9695 100644 --- a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/teams/UserTeamsSelectedPopup.tsx +++ b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/[userId]/teams/UserTeamsSelectedPopup.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import type { TeamRole } from '@/api/asset_interfaces'; +import type { TeamRole } from '@buster/server-shared/teams'; import { useUpdateUserTeams } from '@/api/buster_rest/users/permissions'; import { PermissionAssignTeamRoleButton } from '@/components/features/PermissionComponents'; import { BusterListSelectedOptionPopupContainer } from '@/components/ui/list'; diff --git a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/config.ts b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/config.ts index 171ebb78b..753e2c8fe 100644 --- a/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/config.ts +++ b/apps/web/src/app/app/(settings_layout)/settings/(permissions)/users/config.ts @@ -6,9 +6,10 @@ export const OrganizationUserStatusText: Record = { - dataAdmin: 'Data Admin', - workspaceAdmin: 'Workspace Admin', + data_admin: 'Data Admin', + workspace_admin: 'Workspace Admin', querier: 'Querier', - restrictedQuerier: 'Restricted Querier', - viewer: 'Viewer' + restricted_querier: 'Restricted Querier', + viewer: 'Viewer', + none: 'None' }; diff --git a/apps/web/src/app/app/(settings_layout)/settings/(restricted-width)/profile/page.tsx b/apps/web/src/app/app/(settings_layout)/settings/(restricted-width)/profile/page.tsx index d6900290e..7326a1d92 100644 --- a/apps/web/src/app/app/(settings_layout)/settings/(restricted-width)/profile/page.tsx +++ b/apps/web/src/app/app/(settings_layout)/settings/(restricted-width)/profile/page.tsx @@ -9,14 +9,19 @@ import { SettingsPageHeader } from '../../_components/SettingsPageHeader'; export default function ProfilePage() { const user = useUserConfigContextSelector((state) => state.user); if (!user) return null; - const { name, email, created_at } = user; + const { name, email, created_at, avatar_url } = user; return (
{/* Header Section */}
- +
{name} diff --git a/apps/web/src/components/features/PermissionComponents/PermissionAssignTeamRole.tsx b/apps/web/src/components/features/PermissionComponents/PermissionAssignTeamRole.tsx index f187d005f..1a76c9453 100644 --- a/apps/web/src/components/features/PermissionComponents/PermissionAssignTeamRole.tsx +++ b/apps/web/src/components/features/PermissionComponents/PermissionAssignTeamRole.tsx @@ -1,19 +1,19 @@ import React from 'react'; -import { TeamRole } from '@/api/asset_interfaces'; import { Select, type SelectItem } from '@/components/ui/select'; +import type { TeamRole } from '@buster/server-shared/teams'; export const TEAM_ROLE_OPTIONS: SelectItem<TeamRole>[] = [ { label: 'Manager', - value: TeamRole.MANAGER + value: 'manager' }, { label: 'Member', - value: TeamRole.MEMBER + value: 'member' }, { label: 'Not a Member', - value: TeamRole.NONE + value: 'none' } ]; diff --git a/apps/web/src/components/features/PermissionComponents/PermissionAsssignTeamRoleButton.tsx b/apps/web/src/components/features/PermissionComponents/PermissionAsssignTeamRoleButton.tsx index ba5324007..a2971aa76 100644 --- a/apps/web/src/components/features/PermissionComponents/PermissionAsssignTeamRoleButton.tsx +++ b/apps/web/src/components/features/PermissionComponents/PermissionAsssignTeamRoleButton.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import type { TeamRole } from '@/api/asset_interfaces'; +import type { TeamRole } from '@buster/server-shared/teams'; import { Button } from '@/components/ui/buttons'; import { Dropdown, type DropdownProps } from '@/components/ui/dropdown'; import { CheckDouble } from '@/components/ui/icons'; diff --git a/apps/web/src/components/features/ShareMenu/IndividualSharePerson.tsx b/apps/web/src/components/features/ShareMenu/IndividualSharePerson.tsx index 448528725..3462ecf2c 100644 --- a/apps/web/src/components/features/ShareMenu/IndividualSharePerson.tsx +++ b/apps/web/src/components/features/ShareMenu/IndividualSharePerson.tsx @@ -9,10 +9,11 @@ export const IndividualSharePerson: React.FC<{ name?: string; email: string; role: ShareRole; + avatarURL: string | null; onUpdateShareRole: (email: string, role: ShareRole | null) => void; assetType: ShareAssetType; disabled: boolean; -}> = React.memo(({ name, onUpdateShareRole, email, role, assetType, disabled }) => { +}> = React.memo(({ name, onUpdateShareRole, email, avatarURL, role, assetType, disabled }) => { const isSameEmailName = name === email; const onChangeShareLevel = useMemoizedFn((v: ShareRole | null) => { @@ -25,7 +26,7 @@ export const IndividualSharePerson: React.FC<{ data-testid={`share-person-${email}`}> <div className="flex h-full items-center space-x-1.5 overflow-hidden"> <div className="flex h-full flex-col items-center justify-center"> - <Avatar className="h-[24px] w-[24px]" name={name || email} /> + <Avatar className="h-[24px] w-[24px]" name={name || email} image={avatarURL} /> </div> <div className="flex flex-col space-y-0 overflow-hidden"> <Text truncate className="leading-1.3"> diff --git a/apps/web/src/components/features/auth/ResetPasswordForm.tsx b/apps/web/src/components/features/auth/ResetPasswordForm.tsx index ec2402669..9366806b6 100644 --- a/apps/web/src/components/features/auth/ResetPasswordForm.tsx +++ b/apps/web/src/components/features/auth/ResetPasswordForm.tsx @@ -4,7 +4,7 @@ import type { User } from '@supabase/supabase-js'; import { useRouter } from 'next/navigation'; import type React from 'react'; import { useState } from 'react'; -import type { BusterUserResponse } from '@/api/asset_interfaces/users'; +import type { UserResponse } from '@buster/server-shared/user'; import { Button } from '@/components/ui/buttons'; import { SuccessCard } from '@/components/ui/card/SuccessCard'; import { Input } from '@/components/ui/inputs'; @@ -17,7 +17,7 @@ import { PolicyCheck } from './PolicyCheck'; export const ResetPasswordForm: React.FC<{ supabaseUser: User; - busterUser: BusterUserResponse; + busterUser: UserResponse; resetPassword: (d: { password: string }) => Promise<{ error: string } | undefined>; }> = ({ supabaseUser, busterUser, resetPassword }) => { const [loading, setLoading] = useState(false); diff --git a/apps/web/src/components/features/list/ListUserItem.tsx b/apps/web/src/components/features/list/ListUserItem.tsx index 673dd7b58..dde1112b5 100644 --- a/apps/web/src/components/features/list/ListUserItem.tsx +++ b/apps/web/src/components/features/list/ListUserItem.tsx @@ -2,22 +2,24 @@ import React from 'react'; import { Avatar } from '@/components/ui/avatar'; import { Text } from '@/components/ui/typography'; -export const ListUserItem = React.memo(({ name, email }: { name: string; email: string }) => { - return ( - <div className="flex w-full items-center space-x-2"> - <div className="flex items-center"> - <Avatar size={24} name={name} /> - </div> +export const ListUserItem = React.memo( + ({ name, email, avatarURL }: { name: string; email: string; avatarURL: string | null }) => { + return ( + <div className="flex w-full items-center space-x-2"> + <div className="flex items-center"> + <Avatar size={24} name={name} image={avatarURL} /> + </div> - <div className="flex flex-col justify-center space-y-0"> - {name && <Text>{name}</Text>} - {email && ( - <Text variant="secondary" style={{ fontSize: 12 }}> - {email} - </Text> - )} + <div className="flex flex-col justify-center space-y-0"> + {name && <Text>{name}</Text>} + {email && ( + <Text variant="secondary" style={{ fontSize: 12 }}> + {email} + </Text> + )} + </div> </div> - </div> - ); -}); + ); + } +); ListUserItem.displayName = 'ListUserItem'; diff --git a/apps/web/src/components/features/sidebars/SidebarUserFooter/SidebarUserFooter.tsx b/apps/web/src/components/features/sidebars/SidebarUserFooter/SidebarUserFooter.tsx index 833adfbcb..98b4ff40a 100644 --- a/apps/web/src/components/features/sidebars/SidebarUserFooter/SidebarUserFooter.tsx +++ b/apps/web/src/components/features/sidebars/SidebarUserFooter/SidebarUserFooter.tsx @@ -30,7 +30,8 @@ export const SidebarUserFooter: React.FC = () => { const handleSignOut = useSignOut(); if (!user) return null; - const { name, email } = user; + console.log(user); + const { name, email, avatar_url } = user; if (!name || !email) return null; @@ -41,6 +42,7 @@ export const SidebarUserFooter: React.FC = () => { <AvatarUserButton username={name} email={email} + avatarUrl={avatar_url} className={cn(COLLAPSED_HIDDEN, 'w-full')} /> </div> diff --git a/apps/web/src/components/features/sidebars/useFavoritesSidebarPanel.tsx b/apps/web/src/components/features/sidebars/useFavoritesSidebarPanel.tsx index 7367fae05..b73b08e1d 100644 --- a/apps/web/src/components/features/sidebars/useFavoritesSidebarPanel.tsx +++ b/apps/web/src/components/features/sidebars/useFavoritesSidebarPanel.tsx @@ -1,7 +1,7 @@ import { useParams } from 'next/navigation'; import { useMemo } from 'react'; import { ShareAssetType } from '@buster/server-shared/share'; -import type { BusterUserFavorite } from '@/api/asset_interfaces/users'; +import type { UserFavorite } from '@buster/server-shared/user'; import { useDeleteUserFavorite, useGetUserFavorites, @@ -27,7 +27,7 @@ export const useFavoriteSidebarPanel = () => { updateUserFavorites(itemIds); }); - const isAssetActive = useMemoizedFn((favorite: BusterUserFavorite) => { + const isAssetActive = useMemoizedFn((favorite: UserFavorite) => { const assetType = favorite.asset_type; const id = favorite.id; diff --git a/apps/web/src/components/ui/avatar/Avatar.tsx b/apps/web/src/components/ui/avatar/Avatar.tsx index 70e6aa6ff..7d6b3e67a 100644 --- a/apps/web/src/components/ui/avatar/Avatar.tsx +++ b/apps/web/src/components/ui/avatar/Avatar.tsx @@ -6,7 +6,7 @@ import { Tooltip } from '../tooltip/Tooltip'; import { Avatar as AvatarBase, AvatarFallback, AvatarImage } from './AvatarBase'; export interface AvatarProps { - image?: string | null; + image: string | null | undefined; name?: string | null; className?: string; fallbackClassName?: string; diff --git a/apps/web/src/components/ui/avatar/AvatarUserButton.tsx b/apps/web/src/components/ui/avatar/AvatarUserButton.tsx index b951a28f8..de09e345a 100644 --- a/apps/web/src/components/ui/avatar/AvatarUserButton.tsx +++ b/apps/web/src/components/ui/avatar/AvatarUserButton.tsx @@ -7,7 +7,7 @@ export const AvatarUserButton = React.forwardRef< HTMLDivElement, { username?: string; - avatarUrl?: string; + avatarUrl?: string | null; email?: string; className?: string; } diff --git a/apps/web/src/context/Posthog/BusterPosthogProvider.tsx b/apps/web/src/context/Posthog/BusterPosthogProvider.tsx index 32e1b0e1e..782dde516 100644 --- a/apps/web/src/context/Posthog/BusterPosthogProvider.tsx +++ b/apps/web/src/context/Posthog/BusterPosthogProvider.tsx @@ -5,9 +5,9 @@ import type { PostHogConfig } from 'posthog-js'; import posthog from 'posthog-js'; import { PostHogProvider } from 'posthog-js/react'; import React, { type PropsWithChildren, useEffect } from 'react'; -import type { BusterUserTeam } from '@/api/asset_interfaces'; import { isDev } from '@/config'; import { useUserConfigContextSelector } from '../Users'; +import type { Team } from '@buster/server-shared/teams'; const POSTHOG_KEY = process.env.NEXT_PUBLIC_POSTHOG_KEY; const DEBUG_POSTHOG = false; @@ -44,7 +44,7 @@ const PosthogWrapper: React.FC<PropsWithChildren> = ({ children }) => { const user = useUserConfigContextSelector((state) => state.user); const userTeams = useUserConfigContextSelector((state) => state.userTeams); const userOrganizations = useUserConfigContextSelector((state) => state.userOrganizations); - const team: BusterUserTeam | undefined = userTeams?.[0]; + const team: Team | undefined = userTeams?.[0]; useEffect(() => { if (POSTHOG_KEY && !isServer && user && posthog && team) { diff --git a/apps/web/src/controllers/DatasetPermissionUser/PermissionListUsersContainer.tsx b/apps/web/src/controllers/DatasetPermissionUser/PermissionListUsersContainer.tsx index f6f1f1895..72a0d6339 100644 --- a/apps/web/src/controllers/DatasetPermissionUser/PermissionListUsersContainer.tsx +++ b/apps/web/src/controllers/DatasetPermissionUser/PermissionListUsersContainer.tsx @@ -38,7 +38,7 @@ export const PermissionListUsersContainer: React.FC<{ dataIndex: 'name', width: 270, render: (name: string, user: ListPermissionUsersResponse) => { - return <ListUserItem name={name} email={user.email} />; + return <ListUserItem name={name} email={user.email} avatarURL={user.avatar_url} />; } }, { diff --git a/apps/web/src/controllers/HomePage/NewChatWarning.stories.tsx b/apps/web/src/controllers/HomePage/NewChatWarning.stories.tsx index 51cc6a3ae..022a56415 100644 --- a/apps/web/src/controllers/HomePage/NewChatWarning.stories.tsx +++ b/apps/web/src/controllers/HomePage/NewChatWarning.stories.tsx @@ -1,7 +1,16 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { fn } from '@storybook/test'; -import { BusterOrganizationRole } from '@/api/asset_interfaces/organizations'; import { NewChatWarning } from './NewChatWarning'; +import { z } from 'zod/v4'; +import type { OrganizationRole } from '@buster/server-shared/organization'; + +const OrganizationRoleSchema: Record<OrganizationRole, string> = { + data_admin: 'data_admin', + workspace_admin: 'workspace_admin', + querier: 'querier', + restricted_querier: 'restricted_querier', + viewer: 'viewer', + none: 'none' +}; const meta: Meta<typeof NewChatWarning> = { title: 'Controllers/HomePage/NewChatWarning', @@ -30,7 +39,7 @@ const meta: Meta<typeof NewChatWarning> = { }, userRole: { control: 'select', - options: Object.values(BusterOrganizationRole), + options: Object.values(OrganizationRoleSchema), description: "The user's role in the organization" } } @@ -45,7 +54,7 @@ export const NewUser: Story = { hasDatasets: false, hasDatasources: false, isAdmin: true, - userRole: BusterOrganizationRole.DATA_ADMIN + userRole: OrganizationRoleSchema.data_admin } }; @@ -55,7 +64,7 @@ export const CompleteSetup: Story = { hasDatasets: true, hasDatasources: true, isAdmin: true, - userRole: BusterOrganizationRole.DATA_ADMIN + userRole: OrganizationRoleSchema.data_admin } }; @@ -65,7 +74,7 @@ export const WorkspaceAdminCompleteSetup: Story = { hasDatasets: true, hasDatasources: true, isAdmin: true, - userRole: BusterOrganizationRole.WORKSPACE_ADMIN + userRole: OrganizationRoleSchema.workspace_admin } }; @@ -75,7 +84,7 @@ export const ViewerRole: Story = { hasDatasets: false, hasDatasources: false, isAdmin: false, - userRole: BusterOrganizationRole.VIEWER + userRole: OrganizationRoleSchema.viewer } }; @@ -84,7 +93,7 @@ export const QuerierRole: Story = { hasDatasets: false, hasDatasources: false, isAdmin: false, - userRole: BusterOrganizationRole.QUERIER + userRole: OrganizationRoleSchema.querier } }; @@ -93,6 +102,6 @@ export const RestrictedQuerierRole: Story = { hasDatasets: false, hasDatasources: false, isAdmin: false, - userRole: BusterOrganizationRole.RESTRICTED_QUERIER + userRole: OrganizationRoleSchema.restricted_querier } }; diff --git a/apps/web/src/controllers/HomePage/NewChatWarning.tsx b/apps/web/src/controllers/HomePage/NewChatWarning.tsx index 581fd43c8..b4d6204d9 100644 --- a/apps/web/src/controllers/HomePage/NewChatWarning.tsx +++ b/apps/web/src/controllers/HomePage/NewChatWarning.tsx @@ -5,12 +5,10 @@ import { ArrowUpRight, CircleCheck, AlertWarning } from '@/components/ui/icons'; import { Paragraph, Text } from '@/components/ui/typography'; import { cn } from '@/lib/classMerge'; import type { useNewChatWarning } from './useNewChatWarning'; -import { - BusterOrganizationRoleLabels, - type BusterOrganizationRole -} from '@/api/asset_interfaces/organizations'; +import { BusterOrganizationRoleLabels } from '@/api/asset_interfaces/organizations'; +import type { OrganizationRole } from '@buster/server-shared/organization'; -const translateRole = (role: BusterOrganizationRole) => { +const translateRole = (role: OrganizationRole) => { return BusterOrganizationRoleLabels[role]; }; @@ -157,7 +155,7 @@ const SetupItem = ({ number, status, title, description, link, linkText }: Setup }; interface ContactAdminCardProps { - userRole?: BusterOrganizationRole; + userRole?: OrganizationRole; } const ContactAdminCard = ({ userRole }: ContactAdminCardProps) => { diff --git a/apps/web/src/lib/user.ts b/apps/web/src/lib/user.ts index 898c9d0b0..0c05a1d2e 100644 --- a/apps/web/src/lib/user.ts +++ b/apps/web/src/lib/user.ts @@ -1,7 +1,6 @@ -import { BusterOrganizationRole } from '@/api/asset_interfaces/organizations'; -import type { BusterUserResponse } from '@/api/asset_interfaces/users'; +import type { UserResponse } from '@buster/server-shared/user'; -export const checkIfUserIsAdmin = (userInfo?: BusterUserResponse | null): boolean => { +export const checkIfUserIsAdmin = (userInfo?: UserResponse | null): boolean => { if (!userInfo) return false; const userOrganization = userInfo?.organizations?.[0]; @@ -10,8 +9,5 @@ export const checkIfUserIsAdmin = (userInfo?: BusterUserResponse | null): boolea const userRole = userOrganization.role; - return ( - userRole === BusterOrganizationRole.DATA_ADMIN || - userRole === BusterOrganizationRole.WORKSPACE_ADMIN - ); + return userRole === 'data_admin' || userRole === 'workspace_admin'; }; diff --git a/packages/server-shared/package.json b/packages/server-shared/package.json index f1261cd0c..3523e6ea0 100644 --- a/packages/server-shared/package.json +++ b/packages/server-shared/package.json @@ -40,11 +40,24 @@ "./slack": { "types": "./dist/slack/index.d.ts", "default": "./dist/slack/index.js" + }, + "./user": { + "types": "./dist/user/index.d.ts", + "default": "./dist/user/index.js" + }, + "./organization": { + "types": "./dist/organization/index.d.ts", + "default": "./dist/organization/index.js" + }, + "./teams": { + "types": "./dist/teams/index.d.ts", + "default": "./dist/teams/index.js" } }, "dependencies": { "@buster/vitest-config": "workspace:*", "@buster/typescript-config": "workspace:*", + "@buster/database": "workspace:*", "zod": "catalog:" }, "devDependencies": { diff --git a/packages/server-shared/src/organization/index.ts b/packages/server-shared/src/organization/index.ts new file mode 100644 index 000000000..2951e9c61 --- /dev/null +++ b/packages/server-shared/src/organization/index.ts @@ -0,0 +1,2 @@ +export * from './organization.types'; +export * from './roles.types'; diff --git a/packages/server-shared/src/organization/organization.types.ts b/packages/server-shared/src/organization/organization.types.ts new file mode 100644 index 000000000..f26e4b8ab --- /dev/null +++ b/packages/server-shared/src/organization/organization.types.ts @@ -0,0 +1,14 @@ +import { z } from 'zod/v4'; +import { OrganizationRoleSchema } from './roles.types'; + +export const OrganizationSchema = z.object({ + created_at: z.string(), + id: z.string(), + deleted_at: z.string().nullable(), + domain: z.string(), + name: z.string(), + updated_at: z.string(), + role: OrganizationRoleSchema, +}); + +export type Organization = z.infer<typeof OrganizationSchema>; diff --git a/packages/server-shared/src/organization/roles.types.ts b/packages/server-shared/src/organization/roles.types.ts new file mode 100644 index 000000000..4317f7dba --- /dev/null +++ b/packages/server-shared/src/organization/roles.types.ts @@ -0,0 +1,6 @@ +import { userOrganizationRoleEnum } from '@buster/database'; +import { z } from 'zod/v4'; + +export const OrganizationRoleSchema = z.enum([...userOrganizationRoleEnum.enumValues, 'none']); + +export type OrganizationRole = z.infer<typeof OrganizationRoleSchema>; diff --git a/packages/server-shared/src/share/shareInterfaces.ts b/packages/server-shared/src/share/shareInterfaces.ts index 15e9eee25..bb24e3957 100644 --- a/packages/server-shared/src/share/shareInterfaces.ts +++ b/packages/server-shared/src/share/shareInterfaces.ts @@ -1,19 +1,25 @@ -import { z } from 'zod/v4'; +import { z } from "zod/v4"; export const ShareRoleSchema = z.enum([ - 'owner', //owner of the asset - 'fullAccess', //same as owner, can share with others - 'canEdit', //can edit, cannot share - 'canFilter', //can filter dashboard - 'canView', //can view asset + "owner", //owner of the asset + "fullAccess", //same as owner, can share with others + "canEdit", //can edit, cannot share + "canFilter", //can filter dashboard + "canView", //can view asset ]); -export const ShareAssetTypeSchema = z.enum(['metric', 'dashboard', 'collection', 'chat']); +export const ShareAssetTypeSchema = z.enum([ + "metric", + "dashboard", + "collection", + "chat", +]); export const ShareIndividualSchema = z.object({ - email: z.string().email(), + email: z.string(), role: ShareRoleSchema, name: z.string().optional(), + avatar_url: z.string().nullable(), }); export const ShareConfigSchema = z.object({ diff --git a/packages/server-shared/src/slack/requests.types.ts b/packages/server-shared/src/slack/requests.types.ts index 22f58a13c..1d5359b65 100644 --- a/packages/server-shared/src/slack/requests.types.ts +++ b/packages/server-shared/src/slack/requests.types.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import { z } from 'zod/v4'; // POST /api/v2/slack/auth/init export const InitiateOAuthSchema = z.object({ diff --git a/packages/server-shared/src/teams/index.ts b/packages/server-shared/src/teams/index.ts new file mode 100644 index 000000000..90b508fe2 --- /dev/null +++ b/packages/server-shared/src/teams/index.ts @@ -0,0 +1,3 @@ +export * from './teams.types'; +export * from './responses'; +export * from './requests'; diff --git a/packages/server-shared/src/teams/requests.ts b/packages/server-shared/src/teams/requests.ts new file mode 100644 index 000000000..33ff9af1e --- /dev/null +++ b/packages/server-shared/src/teams/requests.ts @@ -0,0 +1,18 @@ +import { z } from 'zod/v4'; + +export const CreateTeamRequestSchema = z.object({ + name: z.string(), + description: z.string().optional(), +}); + +export type CreateTeamRequest = z.infer<typeof CreateTeamRequestSchema>; + +export const GetTeamListRequestSchema = z.object({ + page_size: z.number().optional(), + page: z.number().optional(), + permission_group_id: z.string().optional(), + user_id: z.string().optional(), + belongs_to: z.boolean().optional(), +}); + +export type GetTeamListRequest = z.infer<typeof GetTeamListRequestSchema>; diff --git a/packages/server-shared/src/teams/responses.ts b/packages/server-shared/src/teams/responses.ts new file mode 100644 index 000000000..5d6cef58a --- /dev/null +++ b/packages/server-shared/src/teams/responses.ts @@ -0,0 +1,6 @@ +import { z } from 'zod/v4'; +import { TeamSchema } from './teams.types'; + +export const TeamListResponseSchema = z.array(TeamSchema); + +export type TeamListResponse = z.infer<typeof TeamListResponseSchema>; diff --git a/packages/server-shared/src/teams/teams.types.ts b/packages/server-shared/src/teams/teams.types.ts new file mode 100644 index 000000000..6f80c1f82 --- /dev/null +++ b/packages/server-shared/src/teams/teams.types.ts @@ -0,0 +1,24 @@ +import { teamRoleEnum } from '@buster/database'; +import { z } from 'zod/v4'; +import { SharingSettingSchema } from '../user/sharing-setting.types'; + +export const TeamRoleSchema = z.enum([...teamRoleEnum.enumValues, 'none']); + +export type TeamRole = z.infer<typeof TeamRoleSchema>; + +export const TeamSchema = z.object({ + id: z.string(), + name: z.string(), + edit_sql: z.boolean(), + email_slack_enabled: z.boolean(), + export_assets: z.boolean(), + organization_id: z.string(), + sharing_settings: SharingSettingSchema, + upload_csv: z.boolean(), + updated_at: z.string(), + created_at: z.string(), + deleted_at: z.string().nullable(), + role: TeamRoleSchema, +}); + +export type Team = z.infer<typeof TeamSchema>; diff --git a/packages/server-shared/src/user/favorites.types.ts b/packages/server-shared/src/user/favorites.types.ts new file mode 100644 index 000000000..204373db0 --- /dev/null +++ b/packages/server-shared/src/user/favorites.types.ts @@ -0,0 +1,11 @@ +import { z } from 'zod/v4'; +import { ShareAssetTypeSchema } from '../share'; + +export const UserFavoriteSchema = z.object({ + id: z.string(), + asset_type: ShareAssetTypeSchema, + index: z.number().optional(), + name: z.string(), +}); + +export type UserFavorite = z.infer<typeof UserFavoriteSchema>; diff --git a/packages/server-shared/src/user/index.ts b/packages/server-shared/src/user/index.ts new file mode 100644 index 000000000..27951208b --- /dev/null +++ b/packages/server-shared/src/user/index.ts @@ -0,0 +1,7 @@ +export * from './request.types'; +export * from './responses.types'; +export * from './users.types'; +export * from './roles.types'; +export * from '../teams/teams.types'; +export * from './sharing-setting.types'; +export * from './favorites.types'; diff --git a/packages/server-shared/src/user/request.types.ts b/packages/server-shared/src/user/request.types.ts new file mode 100644 index 000000000..1e8b0b16b --- /dev/null +++ b/packages/server-shared/src/user/request.types.ts @@ -0,0 +1,49 @@ +import { z } from 'zod/v4'; +import { OrganizationRoleSchema } from '../organization/roles.types'; +import { ShareAssetTypeSchema } from '../share'; + +export const UserRequestSchema = z.object({ + user_id: z.string(), +}); + +export type UserRequest = z.infer<typeof UserRequestSchema>; + +export const UserUpdateRequestSchema = z.object({ + user_id: z.string(), + name: z.string().optional(), + role: OrganizationRoleSchema.optional(), +}); + +export type UserUpdateRequest = z.infer<typeof UserUpdateRequestSchema>; + +export const UserInviteRequestSchema = z.object({ + emails: z.array(z.string().regex(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)), + team_ids: z.array(z.string()).optional(), +}); + +export type UserInviteRequest = z.infer<typeof UserInviteRequestSchema>; + +export const UserCreateFavoriteRequestSchema = z.object({ + id: z.string(), + asset_type: ShareAssetTypeSchema, + index: z.number().optional(), + name: z.string(), +}); + +export type UserCreateFavoriteRequest = z.infer<typeof UserCreateFavoriteRequestSchema>; + +export const UserDeleteFavoriteRequestSchema = z.array(z.string()); + +export type UserDeleteFavoriteRequest = z.infer<typeof UserDeleteFavoriteRequestSchema>; + +export const UserUpdateFavoriteRequestSchema = z.array(z.string()); + +export type UserUpdateFavoriteRequest = z.infer<typeof UserUpdateFavoriteRequestSchema>; + +export const GetUserListRequestSchema = z.object({ + team_id: z.string(), + page: z.number().optional(), + page_size: z.number().optional(), +}); + +export type GetUserListRequest = z.infer<typeof GetUserListRequestSchema>; diff --git a/packages/server-shared/src/user/responses.types.ts b/packages/server-shared/src/user/responses.types.ts new file mode 100644 index 000000000..f5c06c749 --- /dev/null +++ b/packages/server-shared/src/user/responses.types.ts @@ -0,0 +1,27 @@ +import { z } from 'zod/v4'; +import { OrganizationSchema } from '../organization/organization.types'; +import { OrganizationRoleSchema } from '../organization/roles.types'; +import { TeamSchema } from '../teams/teams.types'; +import { UserFavoriteSchema } from './favorites.types'; +import { UserSchema } from './users.types'; + +export const UserResponseSchema = z.object({ + user: UserSchema, + teams: z.array(TeamSchema), + organizations: z.array(OrganizationSchema).nullable(), +}); + +export const UserListResponseSchema = z.array( + z.object({ + email: z.string(), + id: z.string(), + name: z.string(), + role: OrganizationRoleSchema.nullable(), + }) +); + +export const UserFavoriteResponseSchema = z.array(UserFavoriteSchema); + +export type UserResponse = z.infer<typeof UserResponseSchema>; +export type UserListResponse = z.infer<typeof UserListResponseSchema>; +export type UserFavoriteResponse = z.infer<typeof UserFavoriteResponseSchema>; diff --git a/packages/server-shared/src/user/roles.types.ts b/packages/server-shared/src/user/roles.types.ts new file mode 100644 index 000000000..3ebcef1a0 --- /dev/null +++ b/packages/server-shared/src/user/roles.types.ts @@ -0,0 +1,6 @@ +import { userOrganizationRoleEnum } from '@buster/database'; +import { z } from 'zod/v4'; + +export const UserOrganizationRoleSchema = z.enum([...userOrganizationRoleEnum.enumValues, 'none']); + +export type UserOrganizationRole = z.infer<typeof UserOrganizationRoleSchema>; diff --git a/packages/server-shared/src/user/sharing-setting.types.ts b/packages/server-shared/src/user/sharing-setting.types.ts new file mode 100644 index 000000000..ef68a538a --- /dev/null +++ b/packages/server-shared/src/user/sharing-setting.types.ts @@ -0,0 +1,6 @@ +import { sharingSettingEnum } from '@buster/database'; +import { z } from 'zod/v4'; + +export const SharingSettingSchema = z.enum([...sharingSettingEnum.enumValues, 'none']); + +export type SharingSetting = z.infer<typeof SharingSettingSchema>; diff --git a/packages/server-shared/src/user/users.types.ts b/packages/server-shared/src/user/users.types.ts new file mode 100644 index 000000000..e84d28bb6 --- /dev/null +++ b/packages/server-shared/src/user/users.types.ts @@ -0,0 +1,21 @@ +import { z } from "zod/v4"; +import type { UserFavorite } from "./favorites.types"; +import type { UserOrganizationRole } from "./roles.types"; + +export const UserSchema = z.object({ + attributes: z.object({ + organization_id: z.string(), + organization_role: z.custom<UserOrganizationRole>(), + user_email: z.string().email(), + user_id: z.string(), + }), + created_at: z.string(), + email: z.string().email(), + favorites: z.array(z.custom<UserFavorite>()), + id: z.string(), + name: z.string(), + avatar_url: z.string().nullable(), + updated_at: z.string(), +}); + +export type User = z.infer<typeof UserSchema>; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f47df5b69..f863cf4db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -766,6 +766,9 @@ importers: packages/server-shared: dependencies: + '@buster/database': + specifier: workspace:* + version: link:../database '@buster/typescript-config': specifier: workspace:* version: link:../typescript-config