updated user selection via select response return

This commit is contained in:
Berkay 2025-06-18 13:30:09 +03:00
parent b73417a625
commit 0806ce9d65
24 changed files with 895 additions and 2362 deletions

View File

@ -346,7 +346,7 @@ class LoginHandler:
).join(EndpointRestriction, EndpointRestriction.id == Events.endpoint_id).filter().all()
# Get reachable applications
reachable_app_codes = Application2Employee.get_application_codes(employee_id=int(result_with_keys_dict['Employees.id']), db=db_session)
# reachable_app_codes = Application2Employee.get_application_codes(employee_id=int(result_with_keys_dict['Employees.id']), db=db_session)
# Get reachable events
reachable_event_codes = {endpoint_name: function_code for endpoint_name, function_code in filter_endpoints_and_events}
@ -361,6 +361,11 @@ class LoginHandler:
redis_handler = RedisHandlers()
user_uu_id = Users.query.filter(Users.person_id == result_with_keys_dict['People.id']).first().uu_id
redis_result = redis_handler.update_token_at_redis(token=access_token, add_payload=add_company, user_uuid=user_uu_id)
reachable_app_codes = []
if result := RedisHandlers.get_object_from_redis(access_token=access_token):
if result.is_employee and result.selected_company:
if reachable_app_codes_dict := result.reachable_app_codes:
reachable_app_codes = list(reachable_app_codes_dict.get(data.uuid, {}).keys())
return {"selected_uu_id": data.uuid, "reachable_app_codes": reachable_app_codes}
@classmethod
@ -396,10 +401,7 @@ class LoginHandler:
reachable_event_codes = {endpoint_name: function_code for endpoint_name, function_code in filter_endpoints_and_events}
# Get reachable applications
print('selected_build_living_space_first', selected_build_living_space_first.id)
reachable_app_codes = Application2Occupant.get_application_codes(build_living_space_id=selected_build_living_space_first.id, db=db)
print('reachable_app_codes', reachable_app_codes)
# reachable_app_codes = Application2Occupant.get_application_codes(build_living_space_id=selected_build_living_space_first.id, db=db)
build_part = BuildParts.query.filter(BuildParts.id == result_with_keys_dict['BuildParts.id']).first()
if not build_part:
raise ValueError("EYS_0013")
@ -414,10 +416,16 @@ class LoginHandler:
"code": result_with_keys_dict['OccupantTypes.occupant_code']
}
redis_handler = RedisHandlers()
user_uu_id = Users.query.filter(Users.person_id == result_with_keys_dict['People.id']).first().uu_id
redis_handler.update_token_at_redis(token=access_token, add_payload=add_build_living_space, user_uuid=user_uu_id)
return {"selected_uu_id": data.uuid, "reachable_app_codes": reachable_app_codes}
reachable_app_codes = []
if result := RedisHandlers.get_object_from_redis(access_token=access_token):
if result.is_occupant and result.selected_occupant:
if reachable_app_codes_dict := result.reachable_app_codes:
reachable_app_codes = list(reachable_app_codes_dict.get(data.uuid, {}).keys())
print('reachable_app_codes', reachable_app_codes)
return {"selected_uu_id": data.uuid, "reachable_app_codes": reachable_app_codes or []}
@classmethod
def authentication_select_company_or_occupant_type(cls, request: Any, data: Any):

View File

@ -34,6 +34,8 @@
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-toggle": "^1.1.9",
"@radix-ui/react-tooltip": "^1.2.7",
"@tanstack/react-query": "^5.80.7",
"@tanstack/react-table": "^8.21.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
@ -2657,6 +2659,65 @@
"tailwindcss": "4.1.8"
}
},
"node_modules/@tanstack/query-core": {
"version": "5.80.7",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.80.7.tgz",
"integrity": "sha512-s09l5zeUKC8q7DCCCIkVSns8zZrK4ZDT6ryEjxNBFi68G4z2EBobBS7rdOY3r6W1WbUDpc1fe5oY+YO/+2UVUg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/react-query": {
"version": "5.80.7",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.80.7.tgz",
"integrity": "sha512-u2F0VK6+anItoEvB3+rfvTO9GEh2vb00Je05OwlUe/A0lkJBgW1HckiY3f9YZa+jx6IOe4dHPh10dyp9aY3iRQ==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.80.7"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^18 || ^19"
}
},
"node_modules/@tanstack/react-table": {
"version": "8.21.3",
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz",
"integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==",
"license": "MIT",
"dependencies": {
"@tanstack/table-core": "8.21.3"
},
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/@tanstack/table-core": {
"version": "8.21.3",
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz",
"integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tybys/wasm-util": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",

View File

@ -35,6 +35,8 @@
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-toggle": "^1.1.9",
"@radix-ui/react-tooltip": "^1.2.7",
"@tanstack/react-query": "^5.80.7",
"@tanstack/react-table": "^8.21.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",

View File

@ -21,14 +21,13 @@ export async function POST(req: Request): Promise<NextResponse> {
message: validatedLoginBody.error.message,
});
}
const userLogin = await loginViaAccessKeys(dataValidated);
console.log("userLogin", userLogin);
await initFirstSelection(
userLogin.data?.firstSelection,
userLogin.data?.userType
);
if (userLogin.status === 200 || userLogin.status === 202) {
await initFirstSelection(
userLogin.data?.firstSelection,
userLogin.data?.userType
);
return NextResponse.json({
status: 200,
message: "Login successfully completed",

View File

@ -2,6 +2,11 @@ import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
// Import EventEmitter configuration to increase max listeners
import "../utils/eventEmitterConfig";
// Import Redis-specific configuration to fix Commander EventEmitter warning
import "../utils/configureRedis";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],

View File

@ -0,0 +1,491 @@
'use client';
import { FC, useState } from 'react';
import {
useQuery,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
import {
flexRender,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
SortingState,
createColumnHelper,
ColumnDef,
Row,
Cell,
Header,
Table,
} from '@tanstack/react-table';
interface DashboardPageProps {
searchParams: Record<string, any>;
activePageUrl?: string;
userData?: any;
userLoading?: boolean;
userError?: any;
refreshUser?: () => void;
updateUser?: (data: any) => void;
onlineData?: any;
onlineLoading?: boolean;
onlineError?: any;
refreshOnline?: () => void;
updateOnline?: (data: any) => void;
}
// Wrap the component with QueryClientProvider
const queryClient = new QueryClient();
const DPage: FC<DashboardPageProps> = ({
searchParams,
userData,
userLoading,
onlineData,
activePageUrl
}) => {
// Sample data for the dashboard
const statCardsData = [
{
title: 'Users',
count: '36.5k',
icon: 'bx bx-user',
iconColor: 'text-blue-500',
percentage: '4.65%',
isPositive: true
},
{
title: 'Companies',
count: '4.5k',
icon: 'bx bx-building',
iconColor: 'text-yellow-500',
percentage: '1.25%',
isPositive: false
},
{
title: 'Blogs',
count: '12.5k',
icon: 'bx bxl-blogger',
iconColor: 'text-green-500',
percentage: '2.15%',
isPositive: true
},
{
title: 'Revenue',
count: '$35.5k',
icon: 'bx bx-dollar',
iconColor: 'text-pink-500',
percentage: '3.75%',
isPositive: true
}
];
const userRolesColumns = [
{ header: 'Name', accessor: 'name' },
{ header: 'Email', accessor: 'email' },
{
header: 'Role', accessor: 'role',
cell: (value: string) => (
<span className={`py-1 px-2 rounded-md text-xs ${value === 'Admin' ? 'bg-blue-500/10 text-blue-500' : value === 'Editor' ? 'bg-yellow-500/10 text-yellow-500' : 'bg-emerald-500/10 text-emerald-500'}`}>
{value}
</span>
)
}
];
const userRolesData = [
{ name: 'John Doe', email: 'john@example.com', role: 'Admin' },
{ name: 'Jane Smith', email: 'jane@example.com', role: 'Editor' },
{ name: 'Robert Johnson', email: 'robert@example.com', role: 'Customer' },
{ name: 'Emily Davis', email: 'emily@example.com', role: 'Customer' },
{ name: 'Michael Brown', email: 'michael@example.com', role: 'Editor' }
];
const activitiesColumns = [
{ header: 'Name', accessor: 'name' },
{ header: 'Date', accessor: 'date' },
{ header: 'Time', accessor: 'time' }
];
const activitiesData = [
{ name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' },
{ name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' },
{ name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' },
{ name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' },
{ name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' }
];
const earningsData = [
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: true, status: 'Pending' },
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: false, status: 'Withdrawn' },
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: true, status: 'Pending' },
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: false, status: 'Withdrawn' },
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: true, status: 'Pending' }
];
return (
<div className='p-6'>
<QueryClientProvider client={queryClient}>
<TanStackTableExample />
</QueryClientProvider>
</div>
);
};
// TanStack Table Example component
type Person = {
id: number
firstName: string
lastName: string
age: number
visits: number
status: string
progress: number
}
const TanStackTableExample: FC = () => {
// Column definitions using columnHelper
const columnHelper = createColumnHelper<Person>();
const columns = [
columnHelper.accessor('firstName', {
cell: (info) => info.getValue(),
header: () => 'First Name',
footer: (info) => info.column.id,
}),
columnHelper.accessor((row) => row.lastName, {
id: 'lastName',
cell: (info) => info.getValue(),
header: () => 'Last Name',
footer: (info) => info.column.id,
}),
columnHelper.accessor('age', {
header: () => 'Age',
cell: (info) => info.renderValue(),
footer: (info) => info.column.id,
}),
columnHelper.accessor('visits', {
header: () => 'Visits',
footer: (info) => info.column.id,
}),
columnHelper.accessor('status', {
header: 'Status',
cell: (info) => (
<span className={`py-1 px-2 rounded-md text-xs ${info.getValue() === 'Active' ? 'bg-green-500/10 text-green-500' :
info.getValue() === 'Pending' ? 'bg-yellow-500/10 text-yellow-500' :
'bg-red-500/10 text-red-500'
}`}>
{info.getValue()}
</span>
),
footer: (info) => info.column.id,
}),
columnHelper.accessor('progress', {
header: 'Profile Progress',
cell: (info) => (
<div className="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
<div
className="bg-blue-600 h-2.5 rounded-full"
style={{ width: `${info.getValue()}%` }}
></div>
</div>
),
}),
] as ColumnDef<Person>[];
// Fetch data using React Query
const fetchPeople = async (): Promise<Person[]> => {
// In a real app, this would be an API call
// For this example, we'll return mock data
return [
{
id: 1,
firstName: 'John',
lastName: 'Doe',
age: 28,
visits: 10,
status: 'Active',
progress: 80,
},
{
id: 2,
firstName: 'Jane',
lastName: 'Smith',
age: 32,
visits: 5,
status: 'Pending',
progress: 45,
},
{
id: 3,
firstName: 'Robert',
lastName: 'Johnson',
age: 45,
visits: 20,
status: 'Inactive',
progress: 30,
},
{
id: 4,
firstName: 'Emily',
lastName: 'Davis',
age: 27,
visits: 15,
status: 'Active',
progress: 95,
},
{
id: 5,
firstName: 'Michael',
lastName: 'Brown',
age: 39,
visits: 8,
status: 'Pending',
progress: 60,
},
{
id: 6,
firstName: 'Sarah',
lastName: 'Wilson',
age: 34,
visits: 12,
status: 'Active',
progress: 75,
},
{
id: 7,
firstName: 'David',
lastName: 'Miller',
age: 41,
visits: 7,
status: 'Inactive',
progress: 25,
},
{
id: 8,
firstName: 'Jennifer',
lastName: 'Taylor',
age: 29,
visits: 18,
status: 'Active',
progress: 88,
},
{
id: 9,
firstName: 'James',
lastName: 'Anderson',
age: 36,
visits: 9,
status: 'Pending',
progress: 52,
},
{
id: 10,
firstName: 'Lisa',
lastName: 'Thomas',
age: 31,
visits: 14,
status: 'Active',
progress: 70,
},
{
id: 11,
firstName: 'Richard',
lastName: 'Jackson',
age: 47,
visits: 6,
status: 'Inactive',
progress: 15,
},
{
id: 12,
firstName: 'Mary',
lastName: 'White',
age: 25,
visits: 22,
status: 'Active',
progress: 92,
},
{
id: 13,
firstName: 'Thomas',
lastName: 'Harris',
age: 38,
visits: 11,
status: 'Pending',
progress: 48,
},
{
id: 14,
firstName: 'Patricia',
lastName: 'Martin',
age: 33,
visits: 16,
status: 'Active',
progress: 83,
},
{
id: 15,
firstName: 'Charles',
lastName: 'Thompson',
age: 42,
visits: 4,
status: 'Inactive',
progress: 22,
},
];
};
const { data = [], isLoading, error } = useQuery({
queryKey: ['people'],
queryFn: fetchPeople,
});
const [sorting, setSorting] = useState<SortingState>([]);
const table = useReactTable({
data,
columns,
state: {
sorting,
},
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
if (isLoading) return <div className="text-center py-4">Loading data...</div>;
if (error) return <div className="text-center py-4 text-red-500">Error loading data</div>;
return (
<div className="bg-white rounded-lg shadow-md overflow-hidden">
<div className="p-4 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-800">People Data</h2>
<p className="text-sm text-gray-500">TanStack React Query Table Example</p>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th
key={header.id}
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
onClick={header.column.getToggleSortingHandler()}
>
<div className="flex items-center space-x-1">
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
<span>
{{
asc: ' 🔼',
desc: ' 🔽',
}[header.column.getIsSorted() as string] ?? null}
</span>
</div>
</th>
))}
</tr>
))}
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{table.getRowModel().rows.map((row) => (
<tr key={row.id} className="hover:bg-gray-50">
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<div className="px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
<div className="flex-1 flex justify-between sm:hidden">
<button
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50"
>
Previous
</button>
<button
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
className="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50"
>
Next
</button>
</div>
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p className="text-sm text-gray-700">
Showing{' '}
<span className="font-medium">{table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1}</span>{' '}
to{' '}
<span className="font-medium">
{Math.min(
(table.getState().pagination.pageIndex + 1) * table.getState().pagination.pageSize,
table.getRowCount()
)}
</span>{' '}
of <span className="font-medium">{table.getRowCount()}</span> results
</p>
</div>
<div>
<nav className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
<button
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
className="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50"
>
<span className="sr-only">First</span>
</button>
<button
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
className="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50"
>
<span className="sr-only">Previous</span>
</button>
<button
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
className="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50"
>
<span className="sr-only">Next</span>
</button>
<button
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
className="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50"
>
<span className="sr-only">Last</span>
</button>
</nav>
</div>
</div>
</div>
</div>
);
};
export { DPage }

View File

@ -9,20 +9,22 @@ import EarningsTable from '@/components/custom/EarningsTable';
interface DashboardPageProps {
searchParams: Record<string, any>;
activePageUrl?: string;
userData?: any;
userLoading?: boolean;
userError?: any;
refreshUser?: () => void;
updateUser?: (data: any) => void;
onlineData?: any;
onlineLoading?: boolean;
onlineError?: any;
refreshOnline?: () => void;
updateOnline?: (data: any) => void;
activePageUrl?: string;
}
export const DashboardPage: FC<DashboardPageProps> = ({
const DashboardPage: FC<DashboardPageProps> = ({
searchParams,
userData,
userLoading,
@ -165,3 +167,8 @@ export const DashboardPage: FC<DashboardPageProps> = ({
</div>
);
};
export {
DashboardPage
}

View File

@ -11,7 +11,6 @@ import {
MenuStructure,
MenuItemsSectionProps,
} from "./types";
import MenuEmptyState from "./menuEmptyState";
const menuStaticTranslation = {
tr: { menu: "Menü" },

View File

@ -1,8 +1,8 @@
"use server";
import { redis } from "@/lib/redis";
import { functionRetrieveUserSelection } from "@/fetchers/fecther";
import { ClientRedisToken, defaultClientRedisToken } from "@/fetchers/types/context";
import { REDIS_TIMEOUT } from "@/fetchers/base";
import { safeRedisGet, safeRedisSet, safeJsonParse } from "@/utils/redisOperations";
/**
* Gets the complete data from Redis with improved error handling and timeouts
@ -16,14 +16,12 @@ const getCompleteFromRedis = async (): Promise<ClientRedisToken> => {
if (!redisKey) { return defaultClientRedisToken }
if (redisKey === "default") { return defaultClientRedisToken }
try {
const timeoutPromise = new Promise<string | null>((_, reject) => {
setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT);
});
const result = await Promise.race([redis.get(`${redisKey}`), timeoutPromise]);
if (!result) { return defaultClientRedisToken }
try { const parsedResult = JSON.parse(result); return parsedResult } catch (parseError) { return defaultClientRedisToken }
} catch (redisError) { return defaultClientRedisToken }
// Use safe Redis get operation with proper connection handling
const result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT);
if (!result) { return defaultClientRedisToken }
// Use safe JSON parsing
return safeJsonParse<ClientRedisToken>(result, defaultClientRedisToken);
} catch (error) { return defaultClientRedisToken }
}
@ -40,11 +38,8 @@ const setCompleteToRedis = async (completeObject: ClientRedisToken): Promise<boo
if (!decrpytUserSelection) { return false }
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) { return false }
try {
const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) });
await Promise.race([redis.set(redisKey, JSON.stringify(completeObject)), timeoutPromise]);
return true;
} catch (redisError) { return false }
// Use safe Redis set operation with proper connection handling
return await safeRedisSet(redisKey, JSON.stringify(completeObject), REDIS_TIMEOUT);
} catch (error) { return false }
}
@ -58,11 +53,8 @@ const setNewCompleteToRedis = async (completeObject: ClientRedisToken, redisKey:
try {
if (!redisKey) { return false }
if (!completeObject) { return false }
try {
const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) });
await Promise.race([redis.set(redisKey, JSON.stringify(completeObject)), timeoutPromise])
return true;
} catch (redisError) { return false }
// Use safe Redis set operation with proper connection handling
return await safeRedisSet(redisKey, JSON.stringify(completeObject), REDIS_TIMEOUT);
} catch (error) { return false }
}

View File

@ -1,21 +1,27 @@
"use server";
import { redis } from "@/lib/redis";
import { functionRetrieveUserSelection } from "@/fetchers/fecther";
import { ClientSelection, AuthError } from "@/fetchers/types/context";
import { defaultValuesSelection } from "@/fetchers/types/context/selection/validations";
import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch";
import { loginSelectEmployee, loginSelectOccupant } from "@/fetchers/custom/login/login";
import { safeRedisGet, safeJsonParse } from "@/utils/redisOperations";
import { REDIS_TIMEOUT } from "@/fetchers/base";
const getSelectionFromRedis = async (): Promise<ClientSelection> => {
try {
const decrpytUserSelection = await functionRetrieveUserSelection();
const redisKey = decrpytUserSelection?.redisKey;
if (redisKey === "default") { return { selectionList: [], activeSelection: {} } }
const result = await redis.get(`${redisKey}`);
if (!result) { return { selectionList: [], activeSelection: {} } }
const parsedResult = JSON.parse(result);
if (!parsedResult.selection) { return { selectionList: [], activeSelection: {} } }
if (redisKey === "default") { return defaultValuesSelection }
// Use safe Redis get operation with proper connection handling
const result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT);
if (!result) { return defaultValuesSelection }
// Use safe JSON parsing with proper default object type
const parsedResult = safeJsonParse(result, { selection: defaultValuesSelection });
if (!parsedResult.selection) { return defaultValuesSelection }
return parsedResult.selection;
} catch (error) { return { selectionList: [], activeSelection: {} } }
} catch (error) { return defaultValuesSelection }
}
const setActiveSelectionToRedis = async (selectionObject: any) => {
@ -37,11 +43,11 @@ const setActiveSelectionToRedis = async (selectionObject: any) => {
activeSelection: selectionObject
}
})
console.log("oldData", oldData)
if (oldData.online.userType.toUpperCase() === "EMPLOYEE") {
console.log("selectionObject", selectionObject)
await loginSelectEmployee({ uuid: selectionObject.uu_id });
} else if (oldData.online.userType.toUpperCase() === "OCCUPANT") {
console.log("selectionObject", selectionObject.build_living_space_uu_id)
await loginSelectOccupant({ uuid: selectionObject.build_living_space_uu_id });
}
return true;

View File

@ -1,17 +1,22 @@
"use server";
import { redis } from "@/lib/redis";
import { functionRetrieveUserSelection } from "@/fetchers/fecther";
import { ClientSettings, AuthError } from "@/fetchers/types/context";
import { defaultValuesSettings } from "@/fetchers/types/context/settings/validations";
import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch";
import { safeRedisGet, safeJsonParse } from "@/utils/redisOperations";
import { REDIS_TIMEOUT } from "@/fetchers/base";
const getSettingsFromRedis = async (): Promise<ClientSettings> => {
try {
const decrpytUserSelection = await functionRetrieveUserSelection()
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new AuthError("No redis key found");
const result = await redis.get(`${redisKey}`);
// Use safe Redis get operation with proper connection handling
const result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT);
if (!result) throw new AuthError("No data found in redis");
const parsedResult = JSON.parse(result);
// Use safe JSON parsing with proper default object type
const parsedResult = safeJsonParse(result, { settings: defaultValuesSettings });
if (!parsedResult.settings) throw new AuthError("No settings found in redis");
return parsedResult.settings;
} catch (error) {

View File

@ -1,17 +1,22 @@
"use server";
import { redis } from "@/lib/redis";
import { functionRetrieveUserSelection } from "@/fetchers/fecther";
import { ClientUser, AuthError } from "@/fetchers/types/context";
import { defaultValuesUser } from "@/fetchers/types/context/user/validations";
import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch";
import { safeRedisGet, safeJsonParse } from "@/utils/redisOperations";
import { REDIS_TIMEOUT } from "@/fetchers/base";
const getUserFromRedis = async (): Promise<ClientUser> => {
try {
const decrpytUserSelection = await functionRetrieveUserSelection()
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new AuthError("No redis key found");
const result = await redis.get(`${redisKey}`);
// Use safe Redis get operation with proper connection handling
const result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT);
if (!result) throw new AuthError("No data found in redis");
const parsedResult = JSON.parse(result);
// Use safe JSON parsing with proper default object type
const parsedResult = safeJsonParse(result, { user: defaultValuesUser });
if (!parsedResult.user) throw new AuthError("No user found in redis");
return parsedResult.user;
} catch (error) {

View File

@ -1,17 +1,22 @@
"use server";
import { redis } from "@/lib/redis";
import { functionRetrieveUserSelection } from "@/fetchers/fecther";
import { ClientSettings, AuthError } from "@/fetchers/types/context";
import { defaultValuesSettings } from "@/fetchers/types/context/settings/validations";
import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch";
import { safeRedisGet, safeJsonParse } from "@/utils/redisOperations";
import { REDIS_TIMEOUT } from "@/fetchers/base";
const getConfigFromRedis = async (): Promise<ClientSettings> => {
try {
const decrpytUserSelection = await functionRetrieveUserSelection()
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new AuthError("No redis key found");
const result = await redis.get(`${redisKey}`);
// Use safe Redis get operation with proper connection handling
const result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT);
if (!result) throw new AuthError("No data found in redis");
const parsedResult = JSON.parse(result);
// Use safe JSON parsing with proper default object type
const parsedResult = safeJsonParse(result, { settings: defaultValuesSettings });
if (!parsedResult.settings) throw new AuthError("No settings found in redis");
return parsedResult.settings;
} catch (error) {

View File

@ -1,17 +1,22 @@
"use server";
import { redis } from "@/lib/redis";
import { functionRetrieveUserSelection } from "@/fetchers/fecther";
import { ClientMenu, AuthError } from "@/fetchers/types/context";
import { defaultValuesMenu } from "@/fetchers/types/context/menu/validations";
import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch";
import { safeRedisGet, safeJsonParse } from "@/utils/redisOperations";
import { REDIS_TIMEOUT } from "@/fetchers/base";
const getMenuFromRedis = async (): Promise<ClientMenu> => {
try {
const decrpytUserSelection = await functionRetrieveUserSelection()
const redisKey = decrpytUserSelection?.redisKey;
if (!redisKey) throw new AuthError("No redis key found");
const result = await redis.get(`${redisKey}`);
// Use safe Redis get operation with proper connection handling
const result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT);
if (!result) throw new AuthError("No data found in redis");
const parsedResult = JSON.parse(result);
// Use safe JSON parsing with proper default object type
const parsedResult = safeJsonParse(result, { menu: defaultValuesMenu });
if (!parsedResult.menu) throw new AuthError("No menu found in redis");
return parsedResult.menu;
} catch (error) { if (error instanceof AuthError) { throw error } else { throw new AuthError(error instanceof Error ? error.message : "Unknown error") } }

View File

@ -1,8 +1,9 @@
"use server";
import { redis } from "@/lib/redis";
import { functionRetrieveUserSelection } from "@/fetchers/fecther";
import { ClientOnline, AuthError } from "@/fetchers/types/context";
import { defaultValuesOnline } from "@/fetchers/types/context/online/validations";
import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch";
import { safeRedisGet, safeJsonParse } from "@/utils/redisOperations";
import { REDIS_TIMEOUT } from "@/fetchers/base";
/**
@ -18,14 +19,15 @@ const getOnlineFromRedis = async (): Promise<ClientOnline> => {
if (!redisKey) { throw new AuthError('No redis key found') }
if (redisKey === "default") { throw new AuthError('Invalid redis key') }
try {
const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) });
result = await Promise.race([redis.get(`${redisKey}`), timeoutPromise]) as string | null;
// Use safe Redis get operation with proper connection handling
result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT);
} catch (redisError) { throw new AuthError('Failed to access Redis data') }
if (!result) { throw new AuthError('No data found in redis') }
try {
const parsedResult = JSON.parse(result);
// Use safe JSON parsing with proper default object type
const parsedResult = safeJsonParse(result, { online: defaultValuesOnline });
if (!parsedResult.online) { throw new AuthError('No online data found in redis') }
return parsedResult.online;
} catch (parseError) { throw new AuthError('Invalid data format in redis') }
@ -51,8 +53,8 @@ const setOnlineToRedis = async (onlineObject: ClientOnline): Promise<boolean> =>
try { oldData = await getCompleteFromRedis() } catch (error) { throw new AuthError('Failed to retrieve existing data from Redis') }
if (!oldData) { throw new AuthError('No old data found in redis') }
try {
const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) });
await Promise.race([setCompleteToRedis({ ...oldData, online: onlineObject }), timeoutPromise]);
// Use the setCompleteToRedis function which already uses safe Redis operations
await setCompleteToRedis({ ...oldData, online: onlineObject });
return true;
} catch (redisError) { throw new AuthError('Failed to update Redis data') }
} catch (error) { if (error instanceof AuthError) throw error; throw new AuthError(error instanceof Error ? error.message : 'Unknown error') }

View File

@ -62,6 +62,7 @@ async function initRedis(loginRespone: any, firstSelection: any, accessToken: st
async function initFirstSelection(firstSelection: any, userType: string) {
if (userType === "EMPLOYEE") {
const uuid = firstSelection.uu_id;
console.log("uuid", uuid)
await loginSelectEmployee({ uuid });
} else if (userType === "OCCUPANT") {
const uuid = firstSelection.build_living_space_uu_id;
@ -99,8 +100,6 @@ async function loginViaAccessKeys(payload: LoginViaAccessKeys) {
const redisKeyAccess = await nextCrypto.encrypt(redisKey);
const usersSelection = await nextCrypto.encrypt(JSON.stringify({ selected: firstSelection, userType, redisKey }));
await initRedis(loginRespone, firstSelection, accessToken, redisKey);
cookieStore.set({
name: "eys-zzz",
value: accessToken,
@ -117,6 +116,8 @@ async function loginViaAccessKeys(payload: LoginViaAccessKeys) {
...cookieObject,
});
await initRedis(loginRespone, firstSelection, accessToken, redisKey);
try {
return {
completed: true,
@ -156,11 +157,7 @@ async function loginSelectEmployee(payload: LoginSelect) {
const employeeUUID = payload.uuid;
const selectResponse: any = await fetchDataWithToken(urlLoginSelectEndpoint, { uuid: employeeUUID }, "POST", false);
if (selectResponse.status === 200 || selectResponse.status === 202) {
try {
console.log("selectResponse", selectResponse) // Get Menu URL's of Employee
const validUrls = await retrieveValidUrlsOfRestriction()
setMenuToRedis(validUrls)
} catch (error) { }
try { setMenuToRedis(selectResponse.data.reachable_app_codes || []) } catch (error) { }
}
return selectResponse;
}
@ -169,11 +166,7 @@ async function loginSelectOccupant(payload: LoginSelect) {
const livingSpaceUUID = payload.uuid;
const selectResponse: any = await fetchDataWithToken(urlLoginSelectEndpoint, { uuid: livingSpaceUUID }, "POST", false);
if (selectResponse.status === 200 || selectResponse.status === 202) {
try {
console.log("selectResponse", selectResponse) // Get Menu URL's of Occupant
const validUrls = await retrieveValidUrlsOfRestriction()
setMenuToRedis(validUrls)
} catch (error) { }
try { setMenuToRedis(selectResponse.data.reachable_app_codes || []) } catch (error) { }
}
return selectResponse;
}

View File

@ -1,7 +1,8 @@
import { DashboardPage } from "@/components/custom/content/DashboardPage";
// import { DashboardPage } from "@/components/custom/content/DashboardPage";
import { DPage } from "@/components/custom/content/DPage";
const pageIndexMulti: Record<string, Record<string, React.FC<any>>> = {
"/dashboard": { DashboardPage: DashboardPage },
"/dashboard": { DashboardPage: DPage },
};
export { pageIndexMulti };

View File

@ -0,0 +1,66 @@
// Configure Redis client to prevent EventEmitter memory leak warnings
import { EventEmitter } from "events";
// Increase the default maximum listeners for all EventEmitter instances
EventEmitter.defaultMaxListeners = 20;
// This file will be imported in layout.tsx to configure Redis early in the app lifecycle
export const configureRedisEmitters = () => {
try {
// Attempt to dynamically import the Redis client
import("@/lib/redis")
.then((redisModule) => {
const redis = redisModule.redis;
if (!redis) return;
// Use type assertion to access any potential EventEmitter methods
const redisAny = redis as any;
// Configure Redis client's EventEmitter interfaces
if (typeof redisAny.setMaxListeners === "function") {
redisAny.setMaxListeners(20);
}
// Many Redis clients have internal EventEmitter instances with different names
// Check for common patterns like 'command', 'commander', etc.
const possibleEmitterProps = [
"command",
"commander",
"connector",
"connection",
"client",
"subscriber",
];
possibleEmitterProps.forEach((prop) => {
if (
redisAny[prop] &&
typeof redisAny[prop].setMaxListeners === "function"
) {
redisAny[prop].setMaxListeners(20);
}
});
// Handle any nested emitters that might be causing the warning
if (
redisAny._events &&
Object.keys(redisAny._events).includes("connect")
) {
console.log(
"Found connect event listeners, likely source of warning"
);
}
console.log("Redis EventEmitter configured successfully");
})
.catch((err) => {
console.warn("Failed to configure Redis EventEmitter:", err);
});
} catch (error) {
console.warn("Error configuring Redis EventEmitter:", error);
}
};
// Execute configuration immediately
configureRedisEmitters();

View File

@ -0,0 +1,12 @@
// Increase the default maximum listeners for EventEmitter instances
import { EventEmitter } from 'events';
// Set a higher limit for all EventEmitter instances
EventEmitter.defaultMaxListeners = 20;
// Export a function to set max listeners for a specific emitter instance
export const setMaxListeners = (emitter: EventEmitter, max: number = 20): void => {
if (emitter && typeof emitter.setMaxListeners === 'function') {
emitter.setMaxListeners(max);
}
};

View File

@ -0,0 +1,38 @@
// Helper functions for Redis client management
import { EventEmitter } from 'events';
/**
* Safely increases max listeners for any EventEmitter instance
* This is particularly useful for Redis clients that may create many listeners
*/
export function configureRedisEventEmitter(emitter: any, maxListeners: number = 20): void {
if (emitter && typeof emitter.setMaxListeners === 'function') {
emitter.setMaxListeners(maxListeners);
}
// If the emitter has a commander property (common in Redis clients)
if (emitter && emitter.commander && typeof emitter.commander.setMaxListeners === 'function') {
emitter.commander.setMaxListeners(maxListeners);
}
}
/**
* Creates a singleton wrapper for Redis clients to prevent connection duplication
* @param createClientFn Function that creates a Redis client
* @returns A singleton Redis client
*/
export function createRedisSingleton<T>(createClientFn: () => T): () => T {
let instance: T | null = null;
return () => {
if (!instance) {
instance = createClientFn();
// Configure event emitters if applicable
if (instance && typeof (instance as any).setMaxListeners === 'function') {
configureRedisEventEmitter(instance);
}
}
return instance;
};
}

View File

@ -0,0 +1,66 @@
// Redis operations with proper connection management
import { redis } from "@/lib/redis";
/**
* Safely executes a Redis get operation with proper connection handling
* @param key Redis key to retrieve
* @param timeoutMs Timeout in milliseconds
* @returns The value from Redis or null
*/
export async function safeRedisGet(key: string, timeoutMs: number = 5000): Promise<string | null> {
try {
// Create timeout promise
const timeoutPromise = new Promise<string | null>((_, reject) => {
setTimeout(() => reject(new Error('Redis operation timed out')), timeoutMs);
});
// Execute the Redis get operation with timeout
const result = await Promise.race([redis.get(key), timeoutPromise]);
return result;
} catch (error) {
console.error('Redis get operation failed:', error);
return null;
}
}
/**
* Safely executes a Redis set operation with proper connection handling
* @param key Redis key to set
* @param value Value to store
* @param timeoutMs Timeout in milliseconds
* @returns True if successful, false otherwise
*/
export async function safeRedisSet(key: string, value: string, timeoutMs: number = 5000): Promise<boolean> {
try {
// Create timeout promise
const timeoutPromise = new Promise<string>((_, reject) => {
setTimeout(() => reject(new Error('Redis operation timed out')), timeoutMs);
});
// Execute the Redis set operation with timeout
await Promise.race([redis.set(key, value), timeoutPromise]);
return true;
} catch (error) {
console.error('Redis set operation failed:', error);
return false;
}
}
/**
* Safely parses JSON from Redis
* @param jsonString JSON string to parse
* @param defaultValue Default value to return if parsing fails
* @returns Parsed object or default value
*/
export function safeJsonParse<T>(jsonString: string | null, defaultValue: T): T {
if (!jsonString) return defaultValue;
try {
return JSON.parse(jsonString) as T;
} catch (error) {
console.error('Failed to parse JSON from Redis:', error);
return defaultValue;
}
}

File diff suppressed because it is too large Load Diff

48
package-lock.json generated Normal file
View File

@ -0,0 +1,48 @@
{
"name": "production-evyos-systems-and-services-3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@tanstack/react-query": "^5.80.7"
}
},
"node_modules/@tanstack/query-core": {
"version": "5.80.7",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.80.7.tgz",
"integrity": "sha512-s09l5zeUKC8q7DCCCIkVSns8zZrK4ZDT6ryEjxNBFi68G4z2EBobBS7rdOY3r6W1WbUDpc1fe5oY+YO/+2UVUg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/react-query": {
"version": "5.80.7",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.80.7.tgz",
"integrity": "sha512-u2F0VK6+anItoEvB3+rfvTO9GEh2vb00Je05OwlUe/A0lkJBgW1HckiY3f9YZa+jx6IOe4dHPh10dyp9aY3iRQ==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.80.7"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^18 || ^19"
}
},
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
}
}
}

5
package.json Normal file
View File

@ -0,0 +1,5 @@
{
"dependencies": {
"@tanstack/react-query": "^5.80.7"
}
}