Fix: add application_role to account-related fields (#48)
* Fix: fix accounts view, menu * add migration * add application_role to account-related fields
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import type { User } from '@supabase/supabase-js';
|
import type { User } from '@supabase/supabase-js';
|
||||||
|
|
||||||
|
import { ApplicationRole } from '@kit/accounts/types/accounts';
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
@@ -18,6 +19,7 @@ type AccountModel = {
|
|||||||
label: string | null;
|
label: string | null;
|
||||||
value: string | null;
|
value: string | null;
|
||||||
image: string | null;
|
image: string | null;
|
||||||
|
application_role: ApplicationRole | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function TeamAccountLayoutSidebar(props: {
|
export function TeamAccountLayoutSidebar(props: {
|
||||||
@@ -73,7 +75,10 @@ function SidebarContainer(props: {
|
|||||||
|
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<ProfileAccountDropdownContainer user={props.user} />
|
<ProfileAccountDropdownContainer
|
||||||
|
user={props.user}
|
||||||
|
accounts={accounts}
|
||||||
|
/>
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import {
|
import { useMemo } from 'react';
|
||||||
BorderedNavigationMenu,
|
|
||||||
BorderedNavigationMenuItem,
|
|
||||||
} from '@kit/ui/bordered-navigation-menu';
|
|
||||||
|
|
||||||
import { AppLogo } from '~/components/app-logo';
|
import { AppLogo } from '~/components/app-logo';
|
||||||
import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
|
import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
|
||||||
@@ -10,18 +7,22 @@ import { getTeamAccountSidebarConfig } from '~/config/team-account-navigation.co
|
|||||||
// local imports
|
// local imports
|
||||||
import { TeamAccountWorkspace } from '../_lib/server/team-account-workspace.loader';
|
import { TeamAccountWorkspace } from '../_lib/server/team-account-workspace.loader';
|
||||||
import { TeamAccountNotifications } from './team-account-notifications';
|
import { TeamAccountNotifications } from './team-account-notifications';
|
||||||
import { useMemo } from 'react';
|
|
||||||
|
|
||||||
export function TeamAccountNavigationMenu(props: {
|
export function TeamAccountNavigationMenu(props: {
|
||||||
workspace: TeamAccountWorkspace;
|
workspace: TeamAccountWorkspace;
|
||||||
}) {
|
}) {
|
||||||
const { account, user, accounts: rawAccounts } = props.workspace;
|
const { account, user, accounts: rawAccounts } = props.workspace;
|
||||||
|
|
||||||
const accounts = useMemo(() => rawAccounts.map((account) => ({
|
const accounts = useMemo(
|
||||||
|
() =>
|
||||||
|
rawAccounts.map((account) => ({
|
||||||
label: account.name,
|
label: account.name,
|
||||||
value: account.slug,
|
value: account.slug,
|
||||||
image: account.picture_url,
|
image: account.picture_url,
|
||||||
})),[rawAccounts])
|
application_role: account.application_role,
|
||||||
|
})),
|
||||||
|
[rawAccounts],
|
||||||
|
);
|
||||||
|
|
||||||
const routes = getTeamAccountSidebarConfig(account.slug).routes.reduce<
|
const routes = getTeamAccountSidebarConfig(account.slug).routes.reduce<
|
||||||
Array<{
|
Array<{
|
||||||
@@ -48,7 +49,7 @@ export function TeamAccountNavigationMenu(props: {
|
|||||||
<AppLogo />
|
<AppLogo />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={'flex items-center justify-end space-x-2.5 gap-2'}>
|
<div className={'flex items-center justify-end gap-2 space-x-2.5'}>
|
||||||
<TeamAccountNotifications accountId={account.id} userId={user.id} />
|
<TeamAccountNotifications accountId={account.id} userId={user.id} />
|
||||||
<ProfileAccountDropdownContainer
|
<ProfileAccountDropdownContainer
|
||||||
user={user}
|
user={user}
|
||||||
|
|||||||
@@ -45,11 +45,14 @@ function SidebarLayout({
|
|||||||
const data = use(loadTeamWorkspace(account));
|
const data = use(loadTeamWorkspace(account));
|
||||||
const state = use(getLayoutState(account));
|
const state = use(getLayoutState(account));
|
||||||
|
|
||||||
const accounts = data.accounts.map(({ name, slug, picture_url }) => ({
|
const accounts = data.accounts.map(
|
||||||
|
({ name, slug, picture_url, application_role }) => ({
|
||||||
label: name,
|
label: name,
|
||||||
value: slug,
|
value: slug,
|
||||||
image: picture_url,
|
image: picture_url,
|
||||||
}));
|
application_role,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TeamAccountWorkspaceContextProvider value={data}>
|
<TeamAccountWorkspaceContextProvider value={data}>
|
||||||
@@ -91,11 +94,14 @@ function HeaderLayout({
|
|||||||
}>) {
|
}>) {
|
||||||
const data = use(loadTeamWorkspace(account));
|
const data = use(loadTeamWorkspace(account));
|
||||||
|
|
||||||
const accounts = data.accounts.map(({ name, slug, picture_url }) => ({
|
const accounts = data.accounts.map(
|
||||||
|
({ name, slug, picture_url, application_role }) => ({
|
||||||
label: name,
|
label: name,
|
||||||
value: slug,
|
value: slug,
|
||||||
image: picture_url,
|
image: picture_url,
|
||||||
}));
|
application_role,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TeamAccountWorkspaceContextProvider value={data}>
|
<TeamAccountWorkspaceContextProvider value={data}>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import type { User } from '@supabase/supabase-js';
|
import type { User } from '@supabase/supabase-js';
|
||||||
|
|
||||||
import { PersonalAccountDropdown } from '@kit/accounts/personal-account-dropdown';
|
import { PersonalAccountDropdown } from '@kit/accounts/personal-account-dropdown';
|
||||||
|
import { ApplicationRole } from '@kit/accounts/types/accounts';
|
||||||
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
|
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
|
||||||
import { useUser } from '@kit/supabase/hooks/use-user';
|
import { useUser } from '@kit/supabase/hooks/use-user';
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ const paths = {
|
|||||||
home: pathsConfig.app.home,
|
home: pathsConfig.app.home,
|
||||||
admin: pathsConfig.app.admin,
|
admin: pathsConfig.app.admin,
|
||||||
doctor: pathsConfig.app.doctor,
|
doctor: pathsConfig.app.doctor,
|
||||||
personalAccountSettings: pathsConfig.app.personalAccountSettings
|
personalAccountSettings: pathsConfig.app.personalAccountSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
const features = {
|
const features = {
|
||||||
@@ -28,11 +29,13 @@ export function ProfileAccountDropdownContainer(props: {
|
|||||||
id: string | null;
|
id: string | null;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
picture_url: string | null;
|
picture_url: string | null;
|
||||||
|
application_role: ApplicationRole | null;
|
||||||
};
|
};
|
||||||
accounts: {
|
accounts: {
|
||||||
label: string | null;
|
label: string | null;
|
||||||
value: string | null;
|
value: string | null;
|
||||||
image?: string | null;
|
image?: string | null;
|
||||||
|
application_role: ApplicationRole | null;
|
||||||
}[];
|
}[];
|
||||||
}) {
|
}) {
|
||||||
const signOut = useSignOut();
|
const signOut = useSignOut();
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { cn } from '@kit/ui/utils';
|
|||||||
import { toTitleCase } from '~/lib/utils';
|
import { toTitleCase } from '~/lib/utils';
|
||||||
|
|
||||||
import { usePersonalAccountData } from '../hooks/use-personal-account-data';
|
import { usePersonalAccountData } from '../hooks/use-personal-account-data';
|
||||||
|
import { ApplicationRole, ApplicationRoleEnum } from '../types/accounts';
|
||||||
|
|
||||||
const PERSONAL_ACCOUNT_SLUG = 'personal';
|
const PERSONAL_ACCOUNT_SLUG = 'personal';
|
||||||
|
|
||||||
@@ -51,13 +52,13 @@ export function PersonalAccountDropdown({
|
|||||||
id: string | null;
|
id: string | null;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
picture_url: string | null;
|
picture_url: string | null;
|
||||||
application_role: string;
|
application_role: ApplicationRole | null;
|
||||||
};
|
};
|
||||||
accounts: {
|
accounts: {
|
||||||
label: string | null;
|
label: string | null;
|
||||||
value: string | null;
|
value: string | null;
|
||||||
image?: string | null;
|
image?: string | null;
|
||||||
application_role: string;
|
application_role: ApplicationRole | null;
|
||||||
}[];
|
}[];
|
||||||
signOutRequested: () => unknown;
|
signOutRequested: () => unknown;
|
||||||
|
|
||||||
@@ -97,13 +98,14 @@ export function PersonalAccountDropdown({
|
|||||||
|
|
||||||
const isSuperAdmin = useMemo(() => {
|
const isSuperAdmin = useMemo(() => {
|
||||||
const hasAdminRole =
|
const hasAdminRole =
|
||||||
personalAccountData?.application_role === 'super_admin';
|
personalAccountData?.application_role === ApplicationRoleEnum.SuperAdmin;
|
||||||
|
|
||||||
return hasAdminRole && hasTotpFactor;
|
return hasAdminRole && hasTotpFactor;
|
||||||
}, [user, personalAccountData, hasTotpFactor]);
|
}, [user, personalAccountData, hasTotpFactor]);
|
||||||
|
|
||||||
const isDoctor = useMemo(() => {
|
const isDoctor = useMemo(() => {
|
||||||
const hasDoctorRole = personalAccountData?.application_role === 'doctor';
|
const hasDoctorRole =
|
||||||
|
personalAccountData?.application_role === ApplicationRoleEnum.Doctor;
|
||||||
|
|
||||||
return hasDoctorRole && hasTotpFactor;
|
return hasDoctorRole && hasTotpFactor;
|
||||||
}, [user, personalAccountData, hasTotpFactor]);
|
}, [user, personalAccountData, hasTotpFactor]);
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
import { Database } from '@kit/supabase/database';
|
import { Database } from '@kit/supabase/database';
|
||||||
|
|
||||||
export type UserAnalysisElement = Database['medreport']['Tables']['analysis_response_elements']['Row'];
|
export type UserAnalysisElement =
|
||||||
export type UserAnalysisResponse = Database['medreport']['Tables']['analysis_responses']['Row'] & {
|
Database['medreport']['Tables']['analysis_response_elements']['Row'];
|
||||||
|
export type UserAnalysisResponse =
|
||||||
|
Database['medreport']['Tables']['analysis_responses']['Row'] & {
|
||||||
elements: UserAnalysisElement[];
|
elements: UserAnalysisElement[];
|
||||||
};
|
};
|
||||||
export type UserAnalysis = UserAnalysisResponse[];
|
export type UserAnalysis = UserAnalysisResponse[];
|
||||||
|
|
||||||
|
export type ApplicationRole =
|
||||||
|
Database['medreport']['Tables']['accounts']['Row']['application_role'];
|
||||||
|
export enum ApplicationRoleEnum {
|
||||||
|
User = 'user',
|
||||||
|
Doctor = 'doctor',
|
||||||
|
SuperAdmin = 'super_admin',
|
||||||
|
}
|
||||||
@@ -114,7 +114,8 @@ export class TeamAccountsApi {
|
|||||||
role,
|
role,
|
||||||
name,
|
name,
|
||||||
slug,
|
slug,
|
||||||
picture_url
|
picture_url,
|
||||||
|
application_role
|
||||||
)
|
)
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1563,6 +1563,9 @@ export type Database = {
|
|||||||
Views: {
|
Views: {
|
||||||
user_account_workspace: {
|
user_account_workspace: {
|
||||||
Row: {
|
Row: {
|
||||||
|
application_role:
|
||||||
|
| Database["medreport"]["Enums"]["application_role"]
|
||||||
|
| null
|
||||||
id: string | null
|
id: string | null
|
||||||
name: string | null
|
name: string | null
|
||||||
picture_url: string | null
|
picture_url: string | null
|
||||||
@@ -1574,6 +1577,9 @@ export type Database = {
|
|||||||
}
|
}
|
||||||
user_accounts: {
|
user_accounts: {
|
||||||
Row: {
|
Row: {
|
||||||
|
application_role:
|
||||||
|
| Database["medreport"]["Enums"]["application_role"]
|
||||||
|
| null
|
||||||
id: string | null
|
id: string | null
|
||||||
name: string | null
|
name: string | null
|
||||||
picture_url: string | null
|
picture_url: string | null
|
||||||
@@ -1634,7 +1640,9 @@ export type Database = {
|
|||||||
Returns: Json
|
Returns: Json
|
||||||
}
|
}
|
||||||
create_team_account: {
|
create_team_account: {
|
||||||
Args: { account_name: string; new_personal_code: string }
|
Args:
|
||||||
|
| { account_name: string }
|
||||||
|
| { account_name: string; new_personal_code: string }
|
||||||
Returns: {
|
Returns: {
|
||||||
application_role: Database["medreport"]["Enums"]["application_role"]
|
application_role: Database["medreport"]["Enums"]["application_role"]
|
||||||
city: string | null
|
city: string | null
|
||||||
@@ -1801,6 +1809,7 @@ export type Database = {
|
|||||||
primary_owner_user_id: string
|
primary_owner_user_id: string
|
||||||
subscription_status: Database["medreport"]["Enums"]["subscription_status"]
|
subscription_status: Database["medreport"]["Enums"]["subscription_status"]
|
||||||
permissions: Database["medreport"]["Enums"]["app_permissions"][]
|
permissions: Database["medreport"]["Enums"]["app_permissions"][]
|
||||||
|
application_role: Database["medreport"]["Enums"]["application_role"]
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
transfer_team_account_ownership: {
|
transfer_team_account_ownership: {
|
||||||
|
|||||||
76
supabase/migrations/20250814071257_update_accounts_view.sql
Normal file
76
supabase/migrations/20250814071257_update_accounts_view.sql
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
CREATE OR REPLACE VIEW medreport.user_accounts AS
|
||||||
|
SELECT
|
||||||
|
account.id,
|
||||||
|
account.name,
|
||||||
|
account.picture_url,
|
||||||
|
account.slug,
|
||||||
|
membership.account_role AS role,
|
||||||
|
COALESCE(account.application_role, 'user') AS application_role
|
||||||
|
FROM medreport.accounts account
|
||||||
|
JOIN medreport.accounts_memberships membership ON (account.id = membership.account_id)
|
||||||
|
WHERE (
|
||||||
|
membership.user_id = (SELECT auth.uid())
|
||||||
|
AND account.is_personal_account = false
|
||||||
|
AND account.id IN (
|
||||||
|
SELECT accounts_memberships.account_id
|
||||||
|
FROM medreport.accounts_memberships
|
||||||
|
WHERE accounts_memberships.user_id = (SELECT auth.uid())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
GRANT SELECT ON medreport.user_accounts TO authenticated, service_role;
|
||||||
|
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS medreport.team_account_workspace(text);
|
||||||
|
|
||||||
|
CREATE FUNCTION medreport.team_account_workspace(account_slug text)
|
||||||
|
RETURNS TABLE(id uuid, name character varying, picture_url character varying, slug text, role character varying, role_hierarchy_level integer, primary_owner_user_id uuid, subscription_status medreport.subscription_status, permissions medreport.app_permissions[], application_role medreport.application_role)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SET search_path TO ''
|
||||||
|
AS $function$begin
|
||||||
|
return QUERY
|
||||||
|
select
|
||||||
|
accounts.id,
|
||||||
|
accounts.name,
|
||||||
|
accounts.picture_url,
|
||||||
|
accounts.slug,
|
||||||
|
accounts.application_role,
|
||||||
|
accounts_memberships.account_role,
|
||||||
|
roles.hierarchy_level,
|
||||||
|
accounts.primary_owner_user_id,
|
||||||
|
subscriptions.status,
|
||||||
|
array_agg(role_permissions.permission)
|
||||||
|
from
|
||||||
|
medreport.accounts
|
||||||
|
join medreport.accounts_memberships on accounts.id = accounts_memberships.account_id
|
||||||
|
left join medreport.subscriptions on accounts.id = subscriptions.account_id
|
||||||
|
join medreport.roles on accounts_memberships.account_role = roles.name
|
||||||
|
left join medreport.role_permissions on accounts_memberships.account_role = role_permissions.role
|
||||||
|
where
|
||||||
|
accounts.slug = account_slug
|
||||||
|
and medreport.accounts_memberships.user_id = (select auth.uid())
|
||||||
|
group by
|
||||||
|
accounts.id,
|
||||||
|
accounts_memberships.account_role,
|
||||||
|
subscriptions.status,
|
||||||
|
roles.hierarchy_level;
|
||||||
|
end;$function$;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION medreport.team_account_workspace(text) TO authenticated, service_role;
|
||||||
|
|
||||||
|
create or replace view medreport.user_account_workspace as SELECT accounts.id,
|
||||||
|
accounts.name,
|
||||||
|
accounts.picture_url,
|
||||||
|
( SELECT subscriptions.status
|
||||||
|
FROM medreport.subscriptions
|
||||||
|
WHERE (subscriptions.account_id = accounts.id)
|
||||||
|
LIMIT 1) AS subscription_status,
|
||||||
|
accounts.application_role
|
||||||
|
FROM medreport.accounts
|
||||||
|
WHERE ((accounts.primary_owner_user_id = ( SELECT auth.uid() AS uid)) AND (accounts.is_personal_account = true))
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
grant
|
||||||
|
select
|
||||||
|
on medreport.user_account_workspace to authenticated,
|
||||||
|
service_role;
|
||||||
Reference in New Issue
Block a user