Skip to content

Add request education features button and handle upskii-specific services #3087

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/calendar/src/app/[locale]/(auth)/verify-token/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DEV_MODE } from '@/constants/common';
import { TokenVerifier } from '@tuturuuu/auth/cross-app/token-verifier';

export default function VerifyTokenPage() {
return <TokenVerifier />;
return <TokenVerifier devMode={DEV_MODE} />;

Check warning on line 5 in apps/calendar/src/app/[locale]/(auth)/verify-token/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/calendar/src/app/[locale]/(auth)/verify-token/page.tsx#L5

Added line #L5 was not covered by tests
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
create type "public"."platform_service" as enum ('TUTURUUU', 'REWISE', 'NOVA', 'UPSKII');

alter table "public"."users" add column "services" platform_service[];

alter table "public"."users" alter column "services" set default '{TUTURUUU}';

-- Update existing users to have the default service if they don't have any services
UPDATE public.users
SET services = '{TUTURUUU}'::platform_service[]
WHERE services IS NULL;

CREATE OR REPLACE FUNCTION public.validate_cross_app_token_with_session(
p_token TEXT,
p_target_app TEXT
)
RETURNS TABLE(user_id UUID, session_data JSONB) AS $$
DECLARE
v_record RECORD;
v_required_service platform_service;
v_user_services platform_service[];
BEGIN
-- Find the token and get the user_id, session_data, and origin_app if it's valid
SELECT t.user_id, t.session_data, t.origin_app INTO v_record
FROM public.cross_app_tokens t
WHERE t.token = p_token
AND t.target_app = p_target_app
AND t.expires_at > now()
AND t.used_at IS NULL
AND t.is_revoked = false;

-- Log the found record for debugging
RAISE NOTICE 'Found token record: user_id=%, session_data=%, origin_app=%', v_record.user_id, v_record.session_data, v_record.origin_app;

-- If the token is valid, check additional permissions for service access
IF v_record.user_id IS NOT NULL THEN
-- If origin app is web, check that user has the required service for the target app
IF v_record.origin_app = 'web' THEN
-- Map target app to required platform service
CASE p_target_app
WHEN 'platform' THEN v_required_service := 'TUTURUUU';
WHEN 'rewise' THEN v_required_service := 'REWISE';
WHEN 'nova' THEN v_required_service := 'NOVA';
WHEN 'upskii' THEN v_required_service := 'UPSKII';
ELSE v_required_service := NULL;
END CASE;

-- Get user's services
SELECT COALESCE(services, '{}'::platform_service[]) INTO v_user_services
FROM public.users
WHERE id = v_record.user_id;

-- Add the required service if user doesn't have it yet
IF v_required_service IS NOT NULL AND NOT (v_required_service = ANY(v_user_services)) THEN
RAISE NOTICE 'Adding missing service % for user % accessing target app %', v_required_service, v_record.user_id, p_target_app;
-- Add the service to the user's services array
UPDATE public.users
SET services = array_append(services, v_required_service)
WHERE id = v_record.user_id;
END IF;
END IF;

-- Mark token as used
UPDATE public.cross_app_tokens
SET used_at = now()
WHERE token = p_token;

-- Return the user_id and session_data
RETURN QUERY SELECT v_record.user_id, v_record.session_data;
ELSE
-- Return NULL if token is invalid
RETURN QUERY SELECT NULL::UUID, NULL::JSONB;
END IF;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
3 changes: 2 additions & 1 deletion apps/famigo/src/app/[locale]/(auth)/verify-token/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DEV_MODE } from '@/constants/common';
import { TokenVerifier } from '@tuturuuu/auth/cross-app/token-verifier';

export default function VerifyTokenPage() {
return <TokenVerifier />;
return <TokenVerifier devMode={DEV_MODE} />;

Check warning on line 5 in apps/famigo/src/app/[locale]/(auth)/verify-token/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/famigo/src/app/[locale]/(auth)/verify-token/page.tsx#L5

Added line #L5 was not covered by tests
}
3 changes: 2 additions & 1 deletion apps/nova/src/app/[locale]/(auth)/verify-token/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DEV_MODE } from '@/constants/common';
import { TokenVerifier } from '@tuturuuu/auth/cross-app/token-verifier';

export default function VerifyTokenPage() {
return <TokenVerifier />;
return <TokenVerifier devMode={DEV_MODE} />;

Check warning on line 5 in apps/nova/src/app/[locale]/(auth)/verify-token/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/nova/src/app/[locale]/(auth)/verify-token/page.tsx#L5

Added line #L5 was not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,25 @@
if (countError) {
console.error('Error getting count:', countError);
return {
userData: data || [],
userCount: data?.length || 0,
userData: (data || [])
.map((user: any) => ({
...user,
services: user.services || [],
}))
.filter((user: any) => user.services?.includes('NOVA')),
userCount: (data || []).filter((user: any) =>
user.services?.includes('NOVA')
).length,

Check warning on line 147 in apps/nova/src/app/[locale]/(dashboard)/(admin)/(role-management)/users/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/nova/src/app/[locale]/(dashboard)/(admin)/(role-management)/users/page.tsx#L139-L147

Added lines #L139 - L147 were not covered by tests
};
}

return {
userData: data || [],
userData: (data || [])
.map((user: any) => ({
...user,
services: user.services || [],
}))
.filter((user: any) => user.services?.includes('NOVA')),

Check warning on line 157 in apps/nova/src/app/[locale]/(dashboard)/(admin)/(role-management)/users/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/nova/src/app/[locale]/(dashboard)/(admin)/(role-management)/users/page.tsx#L152-L157

Added lines #L152 - L157 were not covered by tests
userCount: countData || 0,
};
}
Expand All @@ -156,6 +168,7 @@
count: 'exact',
}
)
.contains('users.services', ['NOVA'])

Check warning on line 171 in apps/nova/src/app/[locale]/(dashboard)/(admin)/(role-management)/users/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/nova/src/app/[locale]/(dashboard)/(admin)/(role-management)/users/page.tsx#L171

Added line #L171 was not covered by tests
.order('created_at', { ascending: false })
.order('user_id');

Expand Down Expand Up @@ -204,6 +217,7 @@
userData:
data.map(({ nova_team_members, ...user }) => ({
...user,
services: user.services || [],

Check warning on line 220 in apps/nova/src/app/[locale]/(dashboard)/(admin)/(role-management)/users/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/nova/src/app/[locale]/(dashboard)/(admin)/(role-management)/users/page.tsx#L220

Added line #L220 was not covered by tests
team_name:
nova_team_members
?.map((member) => member.team_name)
Expand Down
3 changes: 2 additions & 1 deletion apps/rewise/src/app/[locale]/(auth)/verify-token/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DEV_MODE } from '@/constants/common';
import { TokenVerifier } from '@tuturuuu/auth/cross-app/token-verifier';

export default function VerifyTokenPage() {
return <TokenVerifier />;
return <TokenVerifier devMode={DEV_MODE} />;

Check warning on line 5 in apps/rewise/src/app/[locale]/(auth)/verify-token/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/rewise/src/app/[locale]/(auth)/verify-token/page.tsx#L5

Added line #L5 was not covered by tests
}
3 changes: 2 additions & 1 deletion apps/upskii/src/app/[locale]/(auth)/verify-token/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DEV_MODE } from '@/constants/common';
import { TokenVerifier } from '@tuturuuu/auth/cross-app/token-verifier';

export default function VerifyTokenPage() {
return <TokenVerifier />;
return <TokenVerifier devMode={DEV_MODE} />;

Check warning on line 5 in apps/upskii/src/app/[locale]/(auth)/verify-token/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(auth)/verify-token/page.tsx#L5

Added line #L5 was not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,26 @@

if (countError) {
console.error('Error getting count:', countError);
return { userData: data || [], userCount: data?.length || 0 };
return {
userData: (data || [])
.map((user: any) => ({
...user,
services: user.services || [],
}))
.filter((user: any) => user.services?.includes('UPSKII')),
userCount: (data || []).filter((user: any) =>
user.services?.includes('UPSKII')
).length,
};

Check warning on line 152 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(admin)/(role-management)/users/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(admin)/(role-management)/users/page.tsx#L142-L152

Added lines #L142 - L152 were not covered by tests
}

return {
userData: data || [],
userData: (data || [])
.map((user: any) => ({
...user,
services: user.services || [],
}))
.filter((user: any) => user.services?.includes('UPSKII')),

Check warning on line 161 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(admin)/(role-management)/users/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(admin)/(role-management)/users/page.tsx#L156-L161

Added lines #L156 - L161 were not covered by tests
userCount: countData || 0,
};
}
Expand All @@ -157,6 +172,7 @@
count: 'exact',
}
)
.contains('users.services', ['UPSKII'])

Check warning on line 175 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(admin)/(role-management)/users/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(admin)/(role-management)/users/page.tsx#L175

Added line #L175 was not covered by tests
.order('created_at', { ascending: false })
.order('user_id');

Expand Down Expand Up @@ -205,6 +221,7 @@
userData:
data.map(({ nova_team_members, ...user }) => ({
...user,
services: user.services || [],

Check warning on line 224 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(admin)/(role-management)/users/page.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(admin)/(role-management)/users/page.tsx#L224

Added line #L224 was not covered by tests
team_name:
nova_team_members
?.map((member) => member.team_name)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
'use client';

import { WorkspaceApprovalRequest } from './page';
import { ApprovalRowActions } from './row-actions';
import { ColumnDef } from '@tanstack/react-table';
import { Badge } from '@tuturuuu/ui/badge';
import { DataTableColumnHeader } from '@tuturuuu/ui/custom/tables/data-table-column-header';
import { cn } from '@tuturuuu/utils/format';
import moment from 'moment';

export const approvalsColumns = (
t: any,
namespace: string | undefined
): ColumnDef<WorkspaceApprovalRequest>[] => [
{
accessorKey: 'id',
header: ({ column }) => (
<DataTableColumnHeader
t={t}
column={column}
title={t(`${namespace}.id`)}
/>
),
cell: ({ row }) => (
<div className="line-clamp-1 max-w-32 break-all">
{row.getValue('id')}
</div>
),
},
{
accessorKey: 'workspace_name',
header: ({ column }) => (
<DataTableColumnHeader t={t} column={column} title="Workspace" />
),
cell: ({ row }) => (
<div className="line-clamp-1 max-w-48 font-semibold break-words">
{row.getValue('workspace_name')}
</div>
),
},
{
accessorKey: 'creator_name',
header: ({ column }) => (
<DataTableColumnHeader t={t} column={column} title="Creator" />
),
cell: ({ row }) => {
const creatorName = row.getValue('creator_name') as string;

return (
<div className="flex max-w-48 flex-col gap-1">
<div className="line-clamp-1 font-medium break-words">
{creatorName}
</div>
</div>
);
},
},
{
accessorKey: 'feature_requested',
header: ({ column }) => (
<DataTableColumnHeader t={t} column={column} title="Feature Requested" />
),
cell: ({ row }) => (
<div className="line-clamp-1 max-w-32 break-words">
{row.getValue('feature_requested')}
</div>
),
},
{
accessorKey: 'request_message',
header: ({ column }) => (
<DataTableColumnHeader t={t} column={column} title="Message" />
),
cell: ({ row }) => (
<div className="line-clamp-2 max-w-64 text-sm break-words text-muted-foreground">
{row.getValue('request_message')}
</div>
),
},
{
accessorKey: 'status',
header: ({ column }) => (
<DataTableColumnHeader t={t} column={column} title="Status" />
),
cell: ({ row }) => {
const status = row.getValue('status') as string;

return (
<Badge
variant={
status === 'approved'
? 'default'
: status === 'rejected'
? 'destructive'
: 'secondary'
}
className={cn(
'capitalize',
status === 'approved' &&
'border-green-200 bg-green-100 text-green-800',
status === 'rejected' && 'border-red-200 bg-red-100 text-red-800',
status === 'pending' &&
'border-yellow-200 bg-yellow-100 text-yellow-800'
)}
>
{status}
</Badge>
);
},
},
{
accessorKey: 'created_at',
header: ({ column }) => (
<DataTableColumnHeader t={t} column={column} title="Requested At" />
),
cell: ({ row }) => (
<div className="line-clamp-2 max-w-32 text-sm break-all">
{row.getValue('created_at')
? moment(row.getValue('created_at')).format('DD/MM/YYYY, HH:mm')
: '-'}
</div>
),
},
{
id: 'actions',
header: ({ column }) => <DataTableColumnHeader t={t} column={column} />,
cell: ({ row }) => <ApprovalRowActions row={row} />,
},
];

Check warning on line 129 in apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/columns.tsx

View check run for this annotation

Codecov / codecov/patch

apps/upskii/src/app/[locale]/(dashboard)/[wsId]/(workspace-settings)/approvals/columns.tsx#L2-L129

Added lines #L2 - L129 were not covered by tests
Loading