diff --git a/next.config.js b/next.config.js
index eebdb22..2dd01c7 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,5 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
+ experimental: {
+ authInterrupts: true,
+ },
images: {
remotePatterns: [
{
diff --git a/package.json b/package.json
index fedab9e..4d3d56b 100644
--- a/package.json
+++ b/package.json
@@ -50,6 +50,7 @@
"@supabase/supabase-js": "^2.48.1",
"@tailwindcss/cli": "^4.0.3",
"@tailwindcss/postcss": "^4.0.3",
+ "@tanstack/react-query": "^5.66.8",
"@xyflow/react": "^12.4.2",
"ai": "^4.1.34",
"class-variance-authority": "^0.7.1",
diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts
index 1b8ca12..a4fcd73 100644
--- a/src/app/api/chat/route.ts
+++ b/src/app/api/chat/route.ts
@@ -1,10 +1,20 @@
import { mistral } from '@ai-sdk/mistral';
import { streamText } from 'ai';
-
+import { createClient } from "@/lib/supabase/server"
+import { NextResponse } from "next/server"
// Allow streaming responses up to 30 seconds
export const maxDuration = 30;
export async function POST(req: Request) {
+ const supabase = await createClient()
+
+ const {
+ data: { user },
+ error: userError,
+ } = await supabase.auth.getUser();
+ if (!user || userError) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
+ }
const { messages } = await req.json();
const result = streamText({
diff --git a/src/app/api/investigations/[investigation_id]/data/route.ts b/src/app/api/investigations/[investigation_id]/data/route.ts
new file mode 100644
index 0000000..a4f0c7b
--- /dev/null
+++ b/src/app/api/investigations/[investigation_id]/data/route.ts
@@ -0,0 +1,144 @@
+import { createClient } from "@/lib/supabase/server"
+import type { NodeData, EdgeData } from "@/types"
+import { NextResponse } from "next/server"
+
+export async function GET(_: Request, { params }: { params: Promise<{ investigation_id: string }> }) {
+ const { investigation_id } = await params
+ const supabase = await createClient()
+ try {
+ const {
+ data: { user },
+ error: userError,
+ } = await supabase.auth.getUser();
+ if (!user || userError) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
+ } const { data: individuals, error: indError } = await supabase
+ .from("individuals")
+ .select("*, ip_addresses(*), phone_numbers(*), social_accounts(*), emails(*), physical_addresses(*)")
+ .eq("investigation_id", investigation_id)
+ if (indError) {
+ return NextResponse.json({ error: indError.message }, { status: 500 })
+ }
+ if (!individuals || individuals.length === 0) {
+ return NextResponse.json({ nodes: [], edges: [] })
+ }
+ // Extraire les IDs des individus
+ const individualIds = individuals.map((ind) => ind.id)
+ // Récupérer les relations
+ const { data: relations, error: relError } = await supabase
+ .from("relationships")
+ .select("individual_a, individual_b, relation_type, confidence_level")
+ .in("individual_a", individualIds)
+ .in("individual_b", individualIds)
+ if (relError) {
+ return NextResponse.json({ error: relError.message }, { status: 500 })
+ }
+ const nodes: NodeData[] = []
+ const edges: EdgeData[] = []
+ // Construire les nœuds et les arêtes
+ individuals.forEach((ind: any) => {
+ const individualId = ind.id.toString()
+ nodes.push({
+ id: individualId,
+ type: "individual",
+ data: { ...ind, label: ind.full_name },
+ position: { x: 0, y: 100 },
+ })
+ // Ajouter les emails
+ ind.emails?.forEach((email: any) => {
+ nodes.push({
+ id: email.id.toString(),
+ type: "email",
+ data: { ...email, label: email.email },
+ position: { x: 100, y: 100 },
+ })
+ edges.push({
+ source: individualId,
+ target: email.id.toString(),
+ type: "custom",
+ id: `${individualId}-${email.id}`.toString(),
+ label: "email",
+ })
+ })
+ // Ajouter les numéros de téléphone
+ ind.phone_numbers?.forEach((phone: any) => {
+ nodes.push({
+ id: phone.id.toString(),
+ type: "phone",
+ data: { ...phone, label: phone.phone_number },
+ position: { x: -100, y: 100 },
+ })
+ edges.push({
+ source: individualId,
+ target: phone.id.toString(),
+ type: "custom",
+ id: `${individualId}-${phone.id}`.toString(),
+ label: "phone",
+ })
+ })
+ // Ajouter les comptes sociaux
+ ind.social_accounts?.forEach((social: any) => {
+ nodes.push({
+ id: social.id.toString(),
+ type: "social",
+ data: { ...social, label: `${social.platform}: ${social.username}` },
+ position: { x: 100, y: -100 },
+ })
+ edges.push({
+ source: individualId,
+ target: social.id.toString(),
+ type: "custom",
+ id: `${individualId}-${social.id}`.toString(),
+ label: "social",
+ })
+ })
+ // Ajouter les adresses IP
+ ind.ip_addresses?.forEach((ip: any) => {
+ nodes.push({
+ id: ip.id.toString(),
+ type: "ip",
+ data: { label: ip.ip_address },
+ position: { x: -100, y: -100 },
+ })
+ edges.push({
+ source: individualId,
+ target: ip.id.toString(),
+ type: "custom",
+ id: `${individualId}-${ip.id}`.toString(),
+ label: "IP",
+ })
+ })
+ // Ajouter les adresses physiques
+ ind.physical_addresses?.forEach((address: any) => {
+ nodes.push({
+ id: address.id.toString(),
+ type: "address",
+ data: { ...address, label: [address.address, address.city, address.country].join(", ") },
+ position: { x: 100, y: 100 },
+ })
+ edges.push({
+ source: individualId,
+ target: address.id.toString(),
+ type: "custom",
+ id: `${individualId}-${address.id}`.toString(),
+ label: "address",
+ })
+ })
+ })
+ // Ajouter les relations entre individus
+ relations?.forEach(({ individual_a, individual_b, relation_type, confidence_level }) => {
+ edges.push({
+ source: individual_a.toString(),
+ target: individual_b.toString(),
+ type: "custom",
+ id: `${individual_a}-${individual_b}`.toString(),
+ label: relation_type,
+ confidence_level,
+ })
+ })
+ return NextResponse.json({ nodes, edges })
+ } catch (error) {
+ return NextResponse.json({ error: "Internal Server Error" }, { status: 500 })
+ }
+}
+
diff --git a/src/app/api/investigations/[investigation_id]/emails/route.ts b/src/app/api/investigations/[investigation_id]/emails/route.ts
new file mode 100644
index 0000000..b03739c
--- /dev/null
+++ b/src/app/api/investigations/[investigation_id]/emails/route.ts
@@ -0,0 +1,29 @@
+import { createClient } from "@/lib/supabase/server"
+import { NextResponse } from "next/server"
+
+export async function GET(_: Request, { params }: { params: Promise<{ investigation_id: string }> }) {
+ const { investigation_id } = await params
+ try {
+ const supabase = await createClient()
+ const {
+ data: { user },
+ error: userError,
+ } = await supabase.auth.getUser();
+ if (!user || userError) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
+ }
+ const { data: emails, error } = await supabase
+ .from('emails')
+ .select(`
+ *
+ `)
+ .eq("investigation_id", investigation_id)
+
+ if (error) {
+ return NextResponse.json({ error: error.message }, { status: 500 })
+ }
+ return NextResponse.json({ emails })
+ } catch (error) {
+ return NextResponse.json({ error: "Internal Server Error" }, { status: 500 })
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/investigations/[investigation_id]/individuals/route.ts b/src/app/api/investigations/[investigation_id]/individuals/route.ts
new file mode 100644
index 0000000..8d738ed
--- /dev/null
+++ b/src/app/api/investigations/[investigation_id]/individuals/route.ts
@@ -0,0 +1,29 @@
+import { createClient } from "@/lib/supabase/server"
+import { NextResponse } from "next/server"
+
+export async function GET(_: Request, { params }: { params: Promise<{ investigation_id: string }> }) {
+ const { investigation_id } = await params
+ try {
+ const supabase = await createClient()
+ const {
+ data: { user },
+ error: userError,
+ } = await supabase.auth.getUser();
+ if (!user || userError) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
+ }
+ const { data: individuals, error } = await supabase
+ .from('individuals')
+ .select(`
+ *
+ `)
+ .eq("investigation_id", investigation_id)
+
+ if (error) {
+ return NextResponse.json({ error: error.message }, { status: 500 })
+ }
+ return NextResponse.json({ individuals })
+ } catch (error) {
+ return NextResponse.json({ error: "Internal Server Error" }, { status: 500 })
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/investigations/[investigation_id]/ips/route.ts b/src/app/api/investigations/[investigation_id]/ips/route.ts
new file mode 100644
index 0000000..ee46009
--- /dev/null
+++ b/src/app/api/investigations/[investigation_id]/ips/route.ts
@@ -0,0 +1,27 @@
+import { createClient } from "@/lib/supabase/server"
+import { NextResponse } from "next/server"
+
+export async function GET(_: Request, { params }: { params: Promise<{ investigation_id: string }> }) {
+ const { investigation_id } = await params
+ try {
+ const supabase = await createClient()
+ const {
+ data: { user },
+ error: userError,
+ } = await supabase.auth.getUser();
+ if (!user || userError) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
+ }
+ const { data: ips, error } = await supabase
+ .from('ip_addresses')
+ .select(`*`)
+ .eq("investigation_id", investigation_id)
+
+ if (error) {
+ return NextResponse.json({ error: error.message }, { status: 500 })
+ }
+ return NextResponse.json({ ips })
+ } catch (error) {
+ return NextResponse.json({ error: "Internal Server Error" }, { status: 500 })
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/investigations/[investigation_id]/phones/route.ts b/src/app/api/investigations/[investigation_id]/phones/route.ts
new file mode 100644
index 0000000..701278c
--- /dev/null
+++ b/src/app/api/investigations/[investigation_id]/phones/route.ts
@@ -0,0 +1,27 @@
+import { createClient } from "@/lib/supabase/server"
+import { NextResponse } from "next/server"
+
+export async function GET(_: Request, { params }: { params: Promise<{ investigation_id: string }> }) {
+ const { investigation_id } = await params
+ try {
+ const supabase = await createClient()
+ const {
+ data: { user },
+ error: userError,
+ } = await supabase.auth.getUser();
+ if (!user || userError) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
+ }
+ const { data: phones, error } = await supabase
+ .from('phone_numbers')
+ .select(`*`)
+ .eq("investigation_id", investigation_id)
+
+ if (error) {
+ return NextResponse.json({ error: error.message }, { status: 500 })
+ }
+ return NextResponse.json({ phones })
+ } catch (error) {
+ return NextResponse.json({ error: "Internal Server Error" }, { status: 500 })
+ }
+}
\ No newline at end of file
diff --git a/src/app/api/investigations/[investigation_id]/route.ts b/src/app/api/investigations/[investigation_id]/route.ts
new file mode 100644
index 0000000..b7f6d77
--- /dev/null
+++ b/src/app/api/investigations/[investigation_id]/route.ts
@@ -0,0 +1,31 @@
+import { createClient } from "@/lib/supabase/server"
+import { NextResponse } from "next/server"
+
+export async function GET(_: Request, { params }: { params: Promise<{ investigation_id: string }> }) {
+ const { investigation_id } = await params
+ try {
+ const supabase = await createClient()
+ const {
+ data: { user },
+ error: userError,
+ } = await supabase.auth.getUser();
+ if (!user || userError) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
+ }
+ const { data: investigation, error } = await supabase
+ .from("investigations")
+ .select("id, title, description")
+ .eq("id", investigation_id)
+ .single()
+ if (error) {
+ return NextResponse.json({ error: error.message }, { status: 500 })
+ }
+ if (!investigation) {
+ return NextResponse.json({ error: "Investigation not found" }, { status: 404 })
+ }
+ return NextResponse.json({ investigation })
+ } catch (error) {
+ return NextResponse.json({ error: "Internal Server Error" }, { status: 500 })
+ }
+}
+
diff --git a/src/app/api/investigations/route.ts b/src/app/api/investigations/route.ts
new file mode 100644
index 0000000..d5742e2
--- /dev/null
+++ b/src/app/api/investigations/route.ts
@@ -0,0 +1,23 @@
+import { createClient } from "@/lib/supabase/server"
+import { NextResponse } from "next/server"
+
+export async function GET() {
+ try {
+ const supabase = await createClient()
+ const {
+ data: { user },
+ error: userError,
+ } = await supabase.auth.getUser();
+ if (!user || userError) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
+ }
+ const { data: investigations, error } = await supabase.from("investigations").select("id, title, description")
+ if (error) {
+ return NextResponse.json({ error: error.message }, { status: 500 })
+ }
+ return NextResponse.json({ investigations })
+ } catch (error) {
+ return NextResponse.json({ error: "Internal Server Error" }, { status: 500 })
+ }
+}
+
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index d827642..d075ce0 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -1,13 +1,15 @@
import React from 'react'
-import { getInvestigations } from '@/lib/actions/investigations'
import Investigation from '@/components/dashboard/investigation'
import { Button } from '@/components/ui/button'
import { DownloadIcon, FolderIcon, PlusIcon } from 'lucide-react'
import NewCase from '@/components/dashboard/new-case'
+import { createClient } from '@/lib/supabase/server'
+import { unauthorized } from 'next/navigation'
const DashboardPage = async () => {
- const { investigations, error } = await getInvestigations()
- if (error) return
An error occured.
+ const supabase = await createClient()
+ const { data: investigations, error } = await supabase.from("investigations").select("id, title, description")
+ if (error) return unauthorized()
return (
diff --git a/src/app/investigations/[investigation_id]/client.tsx b/src/app/investigations/[investigation_id]/client.tsx
new file mode 100644
index 0000000..41eaf40
--- /dev/null
+++ b/src/app/investigations/[investigation_id]/client.tsx
@@ -0,0 +1,29 @@
+"use client"
+import { useQuery } from "@tanstack/react-query"
+import InvestigationGraph from "@/components/investigations/graph"
+import IndividualModal from "@/components/investigations/individual-modal"
+import { notFound } from "next/navigation"
+interface DashboardClientProps {
+ investigationId: string
+}
+export default function DashboardClient({ investigationId }: DashboardClientProps) {
+ // Use the initial data from the server, but enable background updates
+ const graphQuery = useQuery({
+ queryKey: ["investigation", investigationId, "data"],
+ queryFn: async () => {
+ const res = await fetch(`/api/investigations/${investigationId}/data`)
+ if (!res.ok) {
+ notFound()
+ }
+ return res.json()
+ },
+ refetchOnWindowFocus: true,
+ })
+ return (
+
+
+
+
+ )
+}
+
diff --git a/src/app/investigations/[investigation_id]/layout.tsx b/src/app/investigations/[investigation_id]/layout.tsx
index 772f124..1ef72ce 100644
--- a/src/app/investigations/[investigation_id]/layout.tsx
+++ b/src/app/investigations/[investigation_id]/layout.tsx
@@ -3,9 +3,8 @@ import InvestigationLayout from '@/components/investigations/layout';
import Left from './left';
import { SearchProvider } from '@/components/contexts/search-context';
import { createClient } from "@/lib/supabase/server";
-import { notFound, redirect } from "next/navigation";
+import { redirect } from "next/navigation";
import { ChatProvider } from '@/components/contexts/chatbot-context';
-import { getInvestigation } from '@/lib/actions/investigations';
const DashboardLayout = async ({
children,
params,
@@ -19,8 +18,6 @@ const DashboardLayout = async ({
redirect('/login')
}
const { investigation_id } = await (params)
- const { investigation, error } = await getInvestigation(investigation_id)
- if (!investigation || error) return notFound()
return (
diff --git a/src/app/investigations/[investigation_id]/left.tsx b/src/app/investigations/[investigation_id]/left.tsx
index 3b59329..ea8e274 100644
--- a/src/app/investigations/[investigation_id]/left.tsx
+++ b/src/app/investigations/[investigation_id]/left.tsx
@@ -3,158 +3,155 @@ import { Skeleton } from "@/components/ui/skeleton"
import { cn } from "@/lib/utils"
import { AtSignIcon, PhoneIcon, UserIcon } from "lucide-react"
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
-import { useInvestigationStore, useInvestigationData } from "@/store/investigation-store"
+import { useInvestigationStore } from "@/store/investigation-store"
import { usePlatformIcons } from "@/lib/hooks/use-platform-icons"
import { useFlowStore } from "@/store/flow-store"
const Left = ({ investigation_id }: { investigation_id: string }) => {
const platformsIcons = usePlatformIcons()
- useInvestigationData(investigation_id)
const { currentNode, setCurrentNode } = useFlowStore()
+
+ // Utiliser le hook useInvestigationData pour récupérer les données
const {
individuals,
- isLoadingIndividuals,
emails,
- isLoadingEmails,
phones,
- isLoadingPhones,
- socials,
- isLoadingSocials,
- } = useInvestigationStore()
+ socials
+ } = useInvestigationStore((state) => state.useInvestigationData(investigation_id))
+
+ // Composant réutilisable pour le skeleton loader
+ const LoadingSkeleton = () => (
+
+
+
+
+
+ )
return (
- Profiles {!isLoadingIndividuals && <>({individuals?.length})>}
+ Profiles {!individuals.isLoading && <>({individuals.data.length})>}
- {isLoadingIndividuals && (
-
-
-
-
-
- )}
-
- {individuals?.map((individual: any) => (
- -
-
-
- ))}
-
+
+
+ ))}
+
+ )}
+
- Emails {!isLoadingEmails && <>({emails?.length})>}
+ Emails {!emails.isLoading && <>({emails.data.length})>}
- {isLoadingEmails && (
-
-
-
-
-
+ {emails.isLoading ? (
+
+ ) : (
+
+ {emails.data.map((email) => (
+ -
+
+
+ ))}
+
)}
-
- {emails?.map((email: any) => (
- -
-
-
- ))}
-
+
- Phones {!isLoadingPhones && <>({phones?.length})>}
+ Phones {!phones.isLoading && <>({phones.data.length})>}
- {isLoadingPhones && (
-
-
-
-
-
+ {phones.isLoading ? (
+
+ ) : (
+
+ {phones.data.map((phone) => (
+ -
+
+
+ ))}
+
)}
-
- {phones?.map((phone: any) => (
- -
-
-
- ))}
-
+
- Socials {!isLoadingSocials && <>({socials?.length})>}
+ Socials {!socials.isLoading && <>({socials.data.length})>}
- {isLoadingSocials && (
-
-
-
-
-
+ {socials.isLoading ? (
+
+ ) : (
+
+ {socials.data.map((social) => (
+ -
+
+
+ ))}
+
)}
-
- {socials?.map((social: any) => (
- -
-
-
- ))}
-
@@ -162,5 +159,4 @@ const Left = ({ investigation_id }: { investigation_id: string }) => {
)
}
-export default Left
-
+export default Left
\ No newline at end of file
diff --git a/src/app/investigations/[investigation_id]/page.tsx b/src/app/investigations/[investigation_id]/page.tsx
index 4bcea2a..2414a07 100644
--- a/src/app/investigations/[investigation_id]/page.tsx
+++ b/src/app/investigations/[investigation_id]/page.tsx
@@ -1,19 +1,24 @@
-import { getInvestigationData } from '@/lib/actions/investigations'
-import InvestigationGraph from '@/components/investigations/graph'
-import IndividualModal from '@/components/investigations/individual-modal'
+import { notFound, unauthorized } from "next/navigation"
+import DashboardClient from "./client"
+import { createClient } from "@/lib/supabase/server"
+// Server Component for initial data fetch
const DashboardPage = async ({
params,
}: {
params: Promise<{ investigation_id: string }>
}) => {
+ const supabase = await createClient()
const { investigation_id } = await (params)
- const { nodes, edges } = await getInvestigationData(investigation_id)
- return (
-
-
-
-
- )
+ const {
+ data: { user },
+ error: userError,
+ } = await supabase.auth.getUser();
+ if (!user || userError) {
+ return notFound()
+ }
+ return
}
-export default DashboardPage
\ No newline at end of file
+
+export default DashboardPage
+
diff --git a/src/app/providers.tsx b/src/app/providers.tsx
index fd6117e..30afcd0 100644
--- a/src/app/providers.tsx
+++ b/src/app/providers.tsx
@@ -3,15 +3,29 @@
import type { ThemeProviderProps } from "next-themes";
import * as React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
+import { useState } from "react"
export interface ProvidersProps {
children: React.ReactNode;
themeProps?: ThemeProviderProps;
}
-
export function Providers({ children, themeProps }: ProvidersProps) {
+ const [queryClient] = useState(
+ () =>
+ new QueryClient({
+ defaultOptions: {
+ queries: {
+ // Disable automatic background refetching
+ staleTime: 60 * 1000, // Consider data stale after 1 minute
+ },
+ },
+ }),
+ )
return (
-
{children}
+
+ {children}
+
);
}
diff --git a/src/components/contexts/investigation-provider.tsx b/src/components/contexts/investigation-provider.tsx
index 4e07121..9389add 100644
--- a/src/components/contexts/investigation-provider.tsx
+++ b/src/components/contexts/investigation-provider.tsx
@@ -3,7 +3,6 @@ import type React from "react"
import { type ReactNode, useEffect } from "react"
import { useInvestigationStore } from "@/store/investigation-store"
import { useParams, useRouter, usePathname, useSearchParams } from "next/navigation"
-import { useInvestigation } from "@/lib/hooks/investigation/investigation"
import { useConfirm } from "@/components/use-confirm-dialog"
import { supabase } from "@/lib/supabase/client"
import { Dialog, DialogContent, DialogDescription, DialogClose, DialogTitle } from "@/components/ui/dialog"
@@ -21,65 +20,49 @@ export const InvestigationProvider: React.FC
= ({ ch
const pathname = usePathname()
const searchParams = useSearchParams()
const { confirm } = useConfirm()
- const { investigation, isLoading: isLoadingInvestigation } = useInvestigation(investigation_id)
-
const {
settings,
setSettings,
openSettingsModal,
setOpenSettingsModal,
- setInvestigation,
- setIsLoadingInvestigation,
setHandleOpenIndividualModal,
setHandleDeleteInvestigation,
} = useInvestigationStore()
useEffect(() => {
- setInvestigation(investigation)
- setIsLoadingInvestigation(isLoadingInvestigation)
-
- const createQueryString = (name: string, value: string) => {
- const params = new URLSearchParams(searchParams.toString())
- params.set(name, value)
- return params.toString()
- }
-
- const handleOpenIndividualModal = (id: string) =>
- router.push(pathname + "?" + createQueryString("individual_id", id))
-
- setHandleOpenIndividualModal(() => handleOpenIndividualModal)
-
const handleDeleteInvestigation = async () => {
- if (
- await confirm({
- title: "Delete investigation",
- message: "Are you really sure you want to delete this investigation ?",
- })
- ) {
- if (
- await confirm({
- title: "Just making sure",
- message: "You will definitely delete all nodes, edges and relationships.",
- })
- ) {
- const { error } = await supabase.from("investigations").delete().eq("id", investigation_id)
- if (error) throw error
- return router.push("/dashboard")
- }
+ const confirmDelete = await confirm({
+ title: "Delete investigation",
+ message: "Are you really sure you want to delete this investigation ?",
+ })
+
+ if (!confirmDelete) return
+
+ const confirmFinal = await confirm({
+ title: "Just making sure",
+ message: "You will definitely delete all nodes, edges and relationships.",
+ })
+ if (!confirmFinal) return
+ try {
+ const { error } = await supabase
+ .from("investigations")
+ .delete()
+ .eq("id", investigation_id)
+
+ if (error) throw error
+
+ router.push("/dashboard")
+ } catch (error) {
+ console.error("Error deleting investigation:", error)
}
}
-
setHandleDeleteInvestigation(handleDeleteInvestigation)
}, [
- investigation,
- isLoadingInvestigation,
investigation_id,
router,
pathname,
searchParams,
confirm,
- setInvestigation,
- setIsLoadingInvestigation,
setHandleOpenIndividualModal,
setHandleDeleteInvestigation,
])
@@ -90,7 +73,13 @@ export const InvestigationProvider: React.FC = ({ ch
title,
description,
disabled = false,
- }: { setting: string; value: boolean; title: string; description: string; disabled?: boolean }) => (
+ }: {
+ setting: keyof typeof settings
+ value: boolean
+ title: string
+ description: string
+ disabled?: boolean
+ }) => (
{title}
@@ -99,7 +88,9 @@ export const InvestigationProvider: React.FC
= ({ ch
setSettings({ ...settings, [setting]: val })}
+ onCheckedChange={(val: boolean) =>
+ setSettings({ ...settings, [setting]: val })
+ }
/>
)
@@ -113,34 +104,34 @@ export const InvestigationProvider: React.FC
= ({ ch
Make changes to your settings.
@@ -156,5 +147,4 @@ export const InvestigationProvider: React.FC
= ({ ch
)
}
-export const useInvestigationContext = useInvestigationStore
-
+export const useInvestigationContext = useInvestigationStore
\ No newline at end of file
diff --git a/src/components/contexts/search-context.tsx b/src/components/contexts/search-context.tsx
index 97a60ab..506f770 100644
--- a/src/components/contexts/search-context.tsx
+++ b/src/components/contexts/search-context.tsx
@@ -44,7 +44,7 @@ export const SearchProvider: React.FC = ({ children }) => {
setIsLoading(true)
setError("")
try {
- const data = await investigateValue(investigation_id as string, value)
+ const data = await investigateValue(value)
setResults(data)
} catch (err) {
setError(err instanceof Error ? err.message : "An error occurred.")
diff --git a/src/components/investigations/case-selector.tsx b/src/components/investigations/case-selector.tsx
index 7bed30c..94068af 100644
--- a/src/components/investigations/case-selector.tsx
+++ b/src/components/investigations/case-selector.tsx
@@ -1,6 +1,6 @@
import { useInvestigations } from "@/lib/hooks/investigation/investigation";
import { useInvestigationStore } from '@/store/investigation-store';
-import { useRouter } from "next/navigation";
+import { useParams, useRouter } from "next/navigation";
import {
Select,
SelectContent,
@@ -12,17 +12,22 @@ import { Skeleton } from "@/components/ui/skeleton";
export default function CaseSelector() {
const router = useRouter()
+ const { investigation_id } = useParams()
+ const useInvestigationData = useInvestigationStore(
+ (state) => state.useInvestigationData
+ );
const { investigations, isLoading } = useInvestigations()
- const { investigation, isLoadingInvestigation } = useInvestigationStore()
+ const { investigation } = useInvestigationData(investigation_id as string);
+
const handleSelectionChange = (value: string) => {
router.push(`/investigations/${value}`);
};
return (
- {isLoading || isLoadingInvestigation ?
:
-
diff --git a/src/components/investigations/nodes/social.tsx b/src/components/investigations/nodes/social.tsx
index 745975a..06f5d31 100644
--- a/src/components/investigations/nodes/social.tsx
+++ b/src/components/investigations/nodes/social.tsx
@@ -74,7 +74,7 @@ function SocialNode({ data }: any) {
diff --git a/src/lib/actions/investigations.ts b/src/lib/actions/investigations.ts
index 7dd09d9..24bcb99 100644
--- a/src/lib/actions/investigations.ts
+++ b/src/lib/actions/investigations.ts
@@ -1,22 +1,14 @@
"use server"
import { createClient } from "../supabase/server"
-import { Investigation } from "@/types/investigation"
-import { NodeData, EdgeData } from "@/types"
-import { notFound, redirect } from "next/navigation"
+import { redirect } from "next/navigation"
import { revalidatePath } from "next/cache"
-interface ReturnTypeGetInvestigations {
- investigations: Investigation[],
- error?: any
-}
-
export async function createNewCase(formData: FormData) {
const supabase = await createClient()
const { data: session, error: userError } = await supabase.auth.getUser()
if (userError || !session?.user) {
redirect('/login')
}
-
const data = {
title: formData.get("title"),
description: formData.get("description"),
@@ -31,164 +23,3 @@ export async function createNewCase(formData: FormData) {
return { success: false, error: "Failed to create new case" }
}
}
-
-export async function getInvestigations(): Promise {
- const supabase = await createClient()
- let { data: investigations, error } = await supabase
- .from('investigations')
- .select('id, title, description')
- return { investigations: investigations as Investigation[], error: error }
-}
-interface ReturnTypeGetInvestigation {
- investigation: Investigation,
- error?: any
-}
-export async function getInvestigation(investigationId: string): Promise {
- const supabase = await createClient()
- let { data: investigation, error } = await supabase
- .from('investigations')
- .select('id, title, description')
- .eq("id", investigationId)
- .single()
- return { investigation: investigation as Investigation, error: error }
-}
-
-export async function getInvestigationData(investigationId: string): Promise<{ nodes: NodeData[], edges: EdgeData[] }> {
- const supabase = await createClient();
- let { data: individuals, error: indError } = await supabase
- .from('individuals')
- .select('*, ip_addresses(*), phone_numbers(*), social_accounts(*), emails(*), physical_addresses(*)')
- .eq('investigation_id', investigationId);
- if (indError) throw notFound();
-
- if (!individuals) individuals = [];
- // Extraire les IDs
- // @ts-ignore
- const individualIds = individuals.map((ind) => ind.id);
-
- if (individualIds.length === 0) {
- return { nodes: [], edges: [] };
- }
-
- let { data: relations, error: relError } = await supabase
- .from('relationships')
- .select('individual_a, individual_b, relation_type, confidence_level')
- .in('individual_a', individualIds)
- .in('individual_b', individualIds);
-
- if (relError) throw relError;
- if (!relations) relations = [];
-
- const nodes: NodeData[] = [];
- const edges: EdgeData[] = [];
-
- // Construire les nœuds des individus
- individuals.forEach((ind: any) => {
- const individualId = ind.id.toString();
- nodes.push({
- id: individualId,
- type: 'individual',
- data: { ...ind, label: ind.full_name },
- position: { x: 0, y: 100 }
- });
-
- // Ajouter les emails
- ind.emails?.forEach((email: any) => {
- nodes.push({
- id: email.id.toString(),
- type: 'email',
- data: { ...email, label: email.email },
- position: { x: 100, y: 100 }
- });
- edges.push({
- source: individualId,
- target: email.id.toString(),
- type: 'custom',
- id: `${individualId}-${email.id}`.toString(),
- label: 'email',
- });
- });
-
- // Ajouter les numéros de téléphone
- ind.phone_numbers?.forEach((phone: any) => {
- nodes.push({
- id: phone.id.toString(),
- type: 'phone',
- data: { ...phone, label: phone.phone_number },
- position: { x: -100, y: 100 }
- });
- edges.push({
- source: individualId,
- target: phone.id.toString(),
- type: 'custom',
- id: `${individualId}-${phone.id}`.toString(),
- label: 'phone',
- });
- });
-
- // Ajouter les comptes sociaux
- ind.social_accounts?.forEach((social: any) => {
- nodes.push({
- id: social.id.toString(),
- type: 'social',
- data: { ...social, label: `${social.platform}: ${social.username}` },
- position: { x: 100, y: -100 }
- });
- edges.push({
- source: individualId,
- target: social.id.toString(),
- type: 'custom',
- id: `${individualId}-${social.id}`.toString(),
- label: 'social',
- });
- });
-
- // Ajouter les adresses IP
- ind.ip_addresses?.forEach((ip: any) => {
- nodes.push({
- id: ip.id.toString(),
- type: 'ip',
- data: { label: ip.ip_address },
- position: { x: -100, y: -100 }
- });
- edges.push({
- source: individualId,
- target: ip.id.toString(),
- type: 'custom',
- id: `${individualId}-${ip.id}`.toString(),
- label: 'IP',
- });
- });
-
- ind.physical_addresses?.forEach((address: any) => {
- nodes.push({
- id: address.id.toString(),
- type: 'address',
- data: { ...address, label: [address.address, address.city, address.country].join(", ") },
- position: { x: 100, y: 100 }
- });
- edges.push({
- source: individualId,
- target: address.id.toString(),
- type: 'custom',
- id: `${individualId}-${address.id}`.toString(),
- label: 'address',
- });
- });
- });
- relations.forEach(({ individual_a, individual_b, relation_type, confidence_level }) => {
- edges.push({
- source: individual_a.toString(),
- target: individual_b.toString(),
- type: 'custom',
- id: `${individual_a}-${individual_b}`.toString(),
- label: relation_type,
- confidence_level: confidence_level
- });
- });
-
- return { nodes, edges };
-}
-
-
-
diff --git a/src/lib/actions/search.ts b/src/lib/actions/search.ts
index 04570ca..36343a3 100644
--- a/src/lib/actions/search.ts
+++ b/src/lib/actions/search.ts
@@ -1,6 +1,5 @@
'use server'
-import { getInvestigation } from "@/lib/actions/investigations"
import { notFound } from "next/navigation"
import { createClient } from "../supabase/server";
@@ -56,14 +55,10 @@ async function checkBreachedAccount(account: string | number | boolean, apiKey:
const apiKey = process.env.HIBP_API_KEY || ""
const appName = 'MyHIBPChecker';
-export async function investigateValue(investigation_id: any, username: string) {
+export async function investigateValue(username: string) {
if (!username) {
throw new Error("Malformed query.")
}
- const { investigation, error } = await getInvestigation(investigation_id)
- if (error || !investigation) {
- notFound()
- }
return checkBreachedAccount(username, apiKey, appName);
}
diff --git a/src/lib/supabase/middleware.ts b/src/lib/supabase/middleware.ts
index 9808ac5..304b266 100644
--- a/src/lib/supabase/middleware.ts
+++ b/src/lib/supabase/middleware.ts
@@ -27,10 +27,12 @@ export async function updateSession(request: NextRequest) {
}
)
- // IMPORTANT: Avoid writing any logic between createServerClient and
+ // Do not run code between createServerClient and
// supabase.auth.getUser(). A simple mistake could make it very hard to debug
// issues with users being randomly logged out.
+ // IMPORTANT: DO NOT REMOVE auth.getUser()
+
const {
data: { user },
} = await supabase.auth.getUser()
@@ -38,7 +40,8 @@ export async function updateSession(request: NextRequest) {
if (
!user &&
!request.nextUrl.pathname.startsWith('/login') &&
- !request.nextUrl.pathname.startsWith('/auth')
+ !request.nextUrl.pathname.startsWith('/auth') &&
+ !request.nextUrl.pathname.startsWith('/api')
) {
// no user, potentially respond by redirecting the user to the login page
const url = request.nextUrl.clone()
@@ -46,8 +49,8 @@ export async function updateSession(request: NextRequest) {
return NextResponse.redirect(url)
}
- // IMPORTANT: You *must* return the supabaseResponse object as it is. If you're
- // creating a new response object with NextResponse.next() make sure to:
+ // IMPORTANT: You *must* return the supabaseResponse object as it is.
+ // If you're creating a new response object with NextResponse.next() make sure to:
// 1. Pass the request in it, like so:
// const myNewResponse = NextResponse.next({ request })
// 2. Copy over the cookies, like so:
diff --git a/src/lib/supabase/server.ts b/src/lib/supabase/server.ts
index a46cfa6..f90cd3a 100644
--- a/src/lib/supabase/server.ts
+++ b/src/lib/supabase/server.ts
@@ -3,11 +3,25 @@ import { cookies } from 'next/headers'
export async function createClient() {
const cookieStore = await cookies()
+ const supabaseToken = cookieStore.get('sb-token')?.value
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
+ auth: {
+ persistSession: false,
+ // Add the token if it exists
+ ...(supabaseToken && {
+ autoRefreshToken: false,
+ detectSessionInUrl: false,
+ storage: {
+ getItem: () => supabaseToken,
+ setItem: () => { },
+ removeItem: () => { },
+ },
+ }),
+ },
cookies: {
getAll() {
return cookieStore.getAll()
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..0606913
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,18 @@
+import { updateSession } from '@/lib/supabase/middleware'
+import { NextRequest } from 'next/server'
+
+export async function middleware(request: NextRequest) {
+ return await updateSession(request)
+}
+export const config = {
+ matcher: [
+ /*
+ * Match all request paths except for the ones starting with:
+ * - _next/static (static files)
+ * - _next/image (image optimization files)
+ * - favicon.ico (favicon file)
+ * Feel free to modify this pattern to include more paths.
+ */
+ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
+ ],
+}
\ No newline at end of file
diff --git a/src/store/flow-store.ts b/src/store/flow-store.ts
index 53bfcde..f4a1de2 100644
--- a/src/store/flow-store.ts
+++ b/src/store/flow-store.ts
@@ -8,7 +8,6 @@ import {
type OnNodesChange,
type OnEdgesChange,
} from '@xyflow/react';
-import { getInvestigationData } from '@/lib/actions/investigations';
export type AppNode = Node;
@@ -29,7 +28,6 @@ export type AppState = {
setCurrentNode: (nodeId: string | null) => void;
updateNode: (nodeId: string, nodeData: Partial) => void;
resetNodeStyles: () => void;
- refreshData: (Investigation_id: string, fitView: () => void) => void,
};
const getLayoutedElements = (nodes: any[], edges: any[], options: { direction: any; }) => {
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
@@ -206,19 +204,6 @@ const createStore = (initialNodes: AppNode[] = [], initialEdges: Edge[] = []) =>
})
});
},
- refreshData: async (investigation_id: string, fitView: () => void) => {
- set({ reloading: true });
- try {
- const { nodes: newNodes, edges: newEdges } = await getInvestigationData(investigation_id);
- set({ nodes: newNodes, edges: newEdges });
- get().onLayout('LR', fitView);
- } catch (error) {
- console.error('Error refreshing data:', error);
- set({ reloading: false });
- } finally {
- set({ reloading: false });
- }
- },
}))
}
export const useFlowStore = createStore();
diff --git a/src/store/investigation-store.ts b/src/store/investigation-store.ts
index 78f69cc..9edacfe 100644
--- a/src/store/investigation-store.ts
+++ b/src/store/investigation-store.ts
@@ -1,67 +1,70 @@
"use client"
-import React from "react"
-
import { create } from "zustand"
import { persist, createJSONStorage } from "zustand/middleware"
-import type { Investigation } from "@/types/investigation"
-import { useIndividuals } from "@/lib/hooks/investigation/use-individuals"
-import { useEmails } from "@/lib/hooks/investigation/use-emails"
-import { usePhones } from "@/lib/hooks/investigation/use-phones"
-import { useSocials } from "@/lib/hooks/investigation/use-socials"
+import { useQuery, useQueryClient, type QueryObserverResult } from "@tanstack/react-query"
+import type { Investigation, Individual, Email, Phone, Social, IP, Relation, Address } from "@/types/investigation"
+
+// Define a generic type for query results
+interface QueryResult {
+ data: T
+ isLoading: boolean
+ refetch: () => Promise
+}
+
+// Define the return type for useInvestigationData
+interface InvestigationData {
+ investigation: QueryResult
+ individuals: QueryResult
+ emails: QueryResult
+ phones: QueryResult
+ socials: QueryResult
+ ips: QueryResult
+ addresses: QueryResult
+ relations: QueryResult
+}
interface InvestigationState {
+ // UI State
filters: any
- setFilters: (filters: any) => void
- settings: any
- setSettings: (settings: any) => void
+ settings: {
+ showNodeLabel: boolean
+ showEdgeLabel: boolean
+ showMiniMap: boolean
+ showCopyIcon: boolean
+ showNodeToolbar: boolean
+ }
openSettingsModal: boolean
- setOpenSettingsModal: (open: boolean) => void
+ currentNode: any
+ panelOpen: boolean
investigation: Investigation | null
+
+ // UI Actions
+ setFilters: (filters: any) => void
+ setSettings: (settings: any) => void
+ setOpenSettingsModal: (open: boolean) => void
+ setCurrentNode: (node: any) => void
+ setPanelOpen: (open: boolean) => void
setInvestigation: (investigation: Investigation | null) => void
- isLoadingInvestigation: boolean | undefined
- setIsLoadingInvestigation: (isLoading: boolean | undefined) => void
+
+ // Modal Handlers
handleOpenIndividualModal: (id: string) => void
setHandleOpenIndividualModal: (handler: (id: string) => void) => void
handleDeleteInvestigation: () => Promise
setHandleDeleteInvestigation: (handler: () => Promise) => void
- currentNode: any
- setCurrentNode: (node: any) => void
- panelOpen: boolean
- setPanelOpen: (open: boolean) => void
- individuals: any[]
- setIndividuals: (individuals: any[]) => void
- isLoadingIndividuals: boolean
- setIsLoadingIndividuals: (isLoading: boolean) => void
- refetchIndividuals: () => void
- setRefetchIndividuals: (refetch: () => void) => void
- emails: any[]
- setEmails: (emails: any[]) => void
- isLoadingEmails: boolean
- setIsLoadingEmails: (isLoading: boolean) => void
- refetchEmails: () => void
- setRefetchEmails: (refetch: () => void) => void
- phones: any[]
- setPhones: (phones: any[]) => void
- isLoadingPhones: boolean
- setIsLoadingPhones: (isLoading: boolean) => void
- refetchPhones: () => void
- setRefetchPhones: (refetch: () => void) => void
- socials: any[]
- setSocials: (socials: any[]) => void
- isLoadingSocials: boolean
- setIsLoadingSocials: (isLoading: boolean) => void
- refetchSocials: () => void
- setRefetchSocials: (refetch: () => void) => void
+
+ // Query Hooks
+ useInvestigationData: (investigationId: string) => InvestigationData
+ refetchAll: (investigationId: string) => Promise
}
const isServer = typeof window === "undefined"
export const useInvestigationStore = create(
persist(
- (set, get) => ({
+ (set, _) => ({
+ // UI State
filters: {},
- setFilters: (filters) => set({ filters }),
settings: {
showNodeLabel: true,
showEdgeLabel: true,
@@ -69,114 +72,127 @@ export const useInvestigationStore = create(
showCopyIcon: true,
showNodeToolbar: true,
},
- setSettings: (settings) => set({ settings }),
openSettingsModal: false,
- setOpenSettingsModal: (open) => set({ openSettingsModal: open }),
+ currentNode: null,
+ panelOpen: false,
investigation: null,
+
+ // UI Actions
+ setFilters: (filters) => set({ filters }),
+ setSettings: (settings) => set({ settings }),
+ setOpenSettingsModal: (open) => set({ openSettingsModal: open }),
+ setCurrentNode: (node) => set({ currentNode: node }),
+ setPanelOpen: (open) => set({ panelOpen: open }),
setInvestigation: (investigation) => set({ investigation }),
- isLoadingInvestigation: undefined,
- setIsLoadingInvestigation: (isLoading) => set({ isLoadingInvestigation: isLoading }),
handleOpenIndividualModal: () => { },
setHandleOpenIndividualModal: (handler) => set({ handleOpenIndividualModal: handler }),
handleDeleteInvestigation: async () => { },
setHandleDeleteInvestigation: (handler) => set({ handleDeleteInvestigation: handler }),
- currentNode: null,
- setCurrentNode: (node) => set({ currentNode: node }),
- panelOpen: false,
- setPanelOpen: (open) => set({ panelOpen: open }),
- individuals: [],
- setIndividuals: (individuals) => set({ individuals }),
- isLoadingIndividuals: true,
- setIsLoadingIndividuals: (isLoading) => set({ isLoadingIndividuals: isLoading }),
- refetchIndividuals: () => { },
- setRefetchIndividuals: (refetch) => set({ refetchIndividuals: refetch }),
- emails: [],
- setEmails: (emails) => set({ emails }),
- isLoadingEmails: true,
- setIsLoadingEmails: (isLoading) => set({ isLoadingEmails: isLoading }),
- refetchEmails: () => { },
- setRefetchEmails: (refetch) => set({ refetchEmails: refetch }),
- phones: [],
- setPhones: (phones) => set({ phones }),
- isLoadingPhones: true,
- setIsLoadingPhones: (isLoading) => set({ isLoadingPhones: isLoading }),
- refetchPhones: () => { },
- setRefetchPhones: (refetch) => set({ refetchPhones: refetch }),
- socials: [],
- setSocials: (socials) => set({ socials }),
- isLoadingSocials: true,
- setIsLoadingSocials: (isLoading) => set({ isLoadingSocials: isLoading }),
- refetchSocials: () => { },
- setRefetchSocials: (refetch) => set({ refetchSocials: refetch }),
+ useInvestigationData: (investigationId: string) => {
+ const wrapRefetch = async (refetch: () => Promise) => {
+ await refetch()
+ }
+ const investigationQuery = useQuery({
+ queryKey: ["investigation", investigationId, 'investigation'],
+ queryFn: async () => {
+ const res = await fetch(`/api/investigations/${investigationId}`)
+ if (!res.ok) throw new Error("Failed to fetch investigation")
+ const data = await res.json()
+ return data.investigation as Investigation
+ },
+ })
+ const individualsQuery = useQuery({
+ queryKey: ["investigation", investigationId, "individuals"],
+ queryFn: async () => {
+ const res = await fetch(`/api/investigations/${investigationId}/individuals`)
+ if (!res.ok) throw new Error("Failed to fetch individuals")
+ const data = await res.json()
+ return data.individuals
+ },
+ })
+ const emailsQuery = useQuery({
+ queryKey: ["investigation", investigationId, "emails"],
+ queryFn: async () => {
+ const res = await fetch(`/api/investigations/${investigationId}/emails`)
+ if (!res.ok) throw new Error("Failed to fetch emails")
+ const data = await res.json()
+ return data.emails
+ },
+ })
+ const phonesQuery = useQuery({
+ queryKey: ["investigation", investigationId, "phones"],
+ queryFn: async () => {
+ const res = await fetch(`/api/investigations/${investigationId}/phones`)
+ if (!res.ok) throw new Error("Failed to fetch phones")
+ const data = await res.json()
+ return data.phones
+ },
+ })
+ return {
+ investigation: {
+ data: investigationQuery.data as Investigation,
+ isLoading: investigationQuery.isLoading,
+ refetch: () => wrapRefetch(investigationQuery.refetch),
+ },
+ individuals: {
+ data: individualsQuery.data ?? [],
+ isLoading: individualsQuery.isLoading,
+ refetch: () => wrapRefetch(individualsQuery.refetch),
+ },
+ emails: {
+ data: emailsQuery.data ?? [],
+ isLoading: emailsQuery.isLoading,
+ refetch: () => wrapRefetch(emailsQuery.refetch),
+ },
+ phones: {
+ data: phonesQuery.data ?? [],
+ isLoading: phonesQuery.isLoading,
+ refetch: () => wrapRefetch(phonesQuery.refetch),
+ },
+ socials: {
+ data: [],
+ isLoading: false,
+ refetch: async () => { },
+ },
+ ips: {
+ data: [],
+ isLoading: false,
+ refetch: async () => { },
+ },
+ addresses: {
+ data: [],
+ isLoading: false,
+ refetch: async () => { },
+ },
+ relations: {
+ data: [],
+ isLoading: false,
+ refetch: async () => { },
+ },
+ }
+ },
+ refetchAll: async (investigationId: string) => {
+ const queryClient = useQueryClient()
+ await Promise.all([
+ queryClient.invalidateQueries({
+ queryKey: ["investigation", investigationId, "individuals"]
+ }),
+ queryClient.invalidateQueries({
+ queryKey: ["investigation", investigationId, "emails"]
+ }),
+ queryClient.invalidateQueries({
+ queryKey: ["investigation", investigationId, "phones"]
+ }),
+ ])
+ },
}),
{
name: "investigation-storage",
storage: createJSONStorage(() => (isServer ? localStorage : localStorage)),
- // @ts-ignore
partialize: (state) => ({
- // Only persist these fields
- filters: state.filters,
- settings: state.settings,
- investigation: state.investigation,
- individuals: state.individuals,
- emails: state.emails,
- phones: state.phones,
- socials: state.socials
+ ...state,
}),
},
- ),
+ )
)
-export const useInvestigationData = (investigation_id: string) => {
- const {
- setIndividuals,
- setIsLoadingIndividuals,
- setRefetchIndividuals,
- setEmails,
- setIsLoadingEmails,
- setRefetchEmails,
- setPhones,
- setIsLoadingPhones,
- setRefetchPhones,
- setSocials,
- setIsLoadingSocials,
- setRefetchSocials,
- } = useInvestigationStore()
-
- const { individuals, isLoading: isLoadingIndividuals, refetch: refetchIndividuals } = useIndividuals(investigation_id)
- const { emails, isLoading: isLoadingEmails, refetch: refetchEmails } = useEmails(investigation_id)
- const { phones, isLoading: isLoadingPhones, refetch: refetchPhones } = usePhones(investigation_id)
- const { socials, isLoading: isLoadingSocials, refetch: refetchSocials } = useSocials(investigation_id)
-
- React.useEffect(() => {
- setIndividuals(individuals || [])
- setIsLoadingIndividuals(isLoadingIndividuals)
- setRefetchIndividuals(() => refetchIndividuals)
- }, [
- individuals,
- isLoadingIndividuals,
- refetchIndividuals,
- setIndividuals,
- setIsLoadingIndividuals,
- setRefetchIndividuals,
- ])
-
- React.useEffect(() => {
- setEmails(emails || [])
- setIsLoadingEmails(isLoadingEmails)
- setRefetchEmails(() => refetchEmails)
- }, [emails, isLoadingEmails, refetchEmails, setEmails, setIsLoadingEmails, setRefetchEmails])
-
- React.useEffect(() => {
- setPhones(phones || [])
- setIsLoadingPhones(isLoadingPhones)
- setRefetchPhones(() => refetchPhones)
- }, [phones, isLoadingPhones, refetchPhones, setPhones, setIsLoadingPhones, setRefetchPhones])
-
- React.useEffect(() => {
- setSocials(socials || [])
- setIsLoadingSocials(isLoadingSocials)
- setRefetchSocials(() => refetchSocials)
- }, [socials, isLoadingSocials, refetchSocials, setSocials, setIsLoadingSocials, setRefetchSocials])
-}
-
diff --git a/src/types/investigation.ts b/src/types/investigation.ts
index e64eeee..22d479c 100644
--- a/src/types/investigation.ts
+++ b/src/types/investigation.ts
@@ -2,4 +2,42 @@ export interface Investigation {
id: string
title: string
description: string
-}
\ No newline at end of file
+}
+export interface Individual {
+ id: string
+ full_name: string
+}
+
+export interface Email {
+ id: string
+ email: string
+}
+
+export interface Phone {
+ id: string,
+ phone_number: string
+}
+
+export interface Social {
+ id: string
+ profile_url: string
+ username: string
+ platform: string
+}
+
+export interface IP {
+ id: string
+ ip_address: string
+}
+
+export interface Address {
+ id: string
+ address: string
+ city: string
+ country: string
+ zip: string
+}
+
+export interface Relation {
+ id: string
+}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index e3840d4..45e4d80 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1568,6 +1568,18 @@
postcss "^8.4.41"
tailwindcss "4.0.3"
+"@tanstack/query-core@5.66.4":
+ version "5.66.4"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.66.4.tgz#44b87bff289466adbfa0de8daa5756cbd2d61c61"
+ integrity sha512-skM/gzNX4shPkqmdTCSoHtJAPMTtmIJNS0hE+xwTTUVYwezArCT34NMermABmBVUg5Ls5aiUXEDXfqwR1oVkcA==
+
+"@tanstack/react-query@^5.66.8":
+ version "5.66.8"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.66.8.tgz#a27494a4ce69c88363eee33637a57e013d7dd5c1"
+ integrity sha512-LqYHYArmM7ycyT1I/Txc/n6KzI8S/hBFw2SQ9Uj1GpbZ89AvZLEvetquiQEHkZ5rFEm+iVNpZ6zYjTiPmJ9N5Q==
+ dependencies:
+ "@tanstack/query-core" "5.66.4"
+
"@types/cookie@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5"