Refresh ignored files

This commit is contained in:
dextmorgn
2025-03-30 22:01:52 +02:00
parent 1dbc5c6bf8
commit a1f25c18ac
28 changed files with 1 additions and 901 deletions

View File

@@ -1,6 +0,0 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@@ -4,7 +4,7 @@ import type React from "react"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { createNewProject } from "@/lib/actions/project"
import { createNewProject } from "@/lib/actions/projects"
import { Button } from "@/components/ui/button"
import {
Dialog,

View File

@@ -1,70 +0,0 @@
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { createClient } from '@/lib/supabase/server'
export async function signInWithGithub() {
const supabase = await createClient()
const { data } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: process.env.NEXT_PUBLIC_AUTH_REDIRECT,
},
})
if (data.url) {
redirect(data.url)
}
}
export async function login(formData: FormData) {
const supabase = await createClient()
// type-casting here for convenience
// in practice, you should validate your inputs
const data = {
email: formData.get('email') as string,
password: formData.get('password') as string,
}
const { error } = await supabase.auth.signInWithPassword(data)
if (error) {
redirect('/error')
}
// revalidatePath('/', 'layout')
// redirect('/')
}
export async function signup(formData: FormData) {
const supabase = await createClient()
// type-casting here for convenience
// in practice, you should validate your inputs
const data = {
email: formData.get('email') as string,
password: formData.get('password') as string,
}
const { error } = await supabase.auth.signUp(data)
if (error) {
redirect('/error')
}
revalidatePath('/', 'layout')
redirect('/')
}
export async function logout() {
const supabase = await createClient()
const { error } = await supabase.auth.signOut()
if (error) {
redirect('/error')
}
revalidatePath('/', 'layout')
redirect('/login')
}

View File

@@ -1,25 +0,0 @@
"use server"
import { createClient } from "../supabase/server"
import { redirect } from "next/navigation"
import { revalidatePath } from "next/cache"
export async function createNewCase(formData: FormData, project_id: string) {
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"),
}
try {
const { data: investigation, error } = await supabase.from("investigations").insert({ ...data, owner_id: session?.user?.id, project_id }).select("id").single()
if (error) throw error
revalidatePath("/")
return { success: true, id: investigation.id }
} catch (error) {
console.error("Error creating new case:", error)
return { success: false, error: "Failed to create new case" }
}
}

View File

@@ -1,77 +0,0 @@
'use server'
import { createClient } from "../supabase/server";
export async function checkEmail(email: string, investigation_id: string) {
const url = `${process.env.NEXT_PUBLIC_DOCKER_FLOWSINT_API}/scan/`;
const response = await fetch(url, {
method: 'POST',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"email": email,
"investigation_id": investigation_id
})
},
);
return response.json();
}
async function checkBreachedAccount(account: string | number | boolean, apiKey: string, appName: string) {
const url = `https://haveibeenpwned.com/api/v3/breachedaccount/${encodeURIComponent(account)}?truncateResponse=false`;
const supabase = await createClient()
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'hibp-api-key': apiKey,
'user-agent': appName
}
});
if (response.ok) {
const data = await response.json() as any[];
for (const breach of data) {
// insert a new breach if not exist
const { error } = await supabase.from('breaches').upsert({
name: breach.Name, description: breach.Description, raw_content: breach
}, { onConflict: 'name' })
if (error) return error
// then get the breach and upsert a new relation with account
const { data: br, error: breachError } = await supabase.from('breaches')
.select('id')
.eq("name", breach.Name).single()
if (breachError) return breachError
if (br) {
// finally insert new relation between account and breach
const { error: relationError } = await supabase.from('accounts_breaches').upsert({
account: account, breach_id: br.id
}, {
onConflict: 'account,breach_id'
})
if (relationError) return relationError
}
}
return data;
} else if (response.status === 404) {
return `Good news! Account ${account} hasn't been found in any breaches.`;
} else {
return `Error: ${response.status} - ${response.statusText}`;
}
} catch (error) {
console.error('An error occurred:', error);
return null;
}
}
const apiKey = process.env.HIBP_API_KEY || ""
const appName = 'MyHIBPChecker';
export async function investigateHIBPwd(username: string) {
if (!username) {
throw new Error("Malformed query.")
}
return checkBreachedAccount(username, apiKey, appName);
}

View File

@@ -1,39 +0,0 @@
import { supabase } from "@/lib/supabase/client"
import { useQuery } from "@supabase-cache-helpers/postgrest-swr"
export function useEmailsAndBreaches(individualId: string | null | undefined) {
const { data: emails, error } = useQuery(
individualId ? supabase.from("individuals").select(`*, emails(*)`).eq("id", individualId).single() : null,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
},
)
const emailAddresses = emails?.emails?.map(({ email }: { email: string }) => email) || []
const { data: breaches, error: breachesError, mutate } = useQuery(
emailAddresses.length > 0
? supabase.from("accounts_breaches").select(`account, breach:breach_id(raw_content)`).in("account", emailAddresses)
: null,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
},
)
const formattedData = emailAddresses.map((email: string) => ({
email,
breaches: breaches?.filter((breach: any) => breach.account === email).map((breach: any) => breach.breach.raw_content) || [],
}))
const isLoading = !emails && !error
return {
emails: formattedData,
isLoading,
refetch: mutate,
error: error || breachesError,
}
}

View File

@@ -1,29 +0,0 @@
import { supabase } from "@/lib/supabase/client";
import { useQuery } from "@supabase-cache-helpers/postgrest-swr";
export function useIndividual(individualId: string | null | undefined) {
const { data: individual, mutate, isLoading, error } = useQuery(Boolean(individualId) ?
supabase
.from('individuals')
.select(`
*,
ip_addresses(*),
phone_numbers(*),
social_accounts(*),
emails(*)
`)
.eq("id", individualId)
.single() : null,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
return {
individual: individual || null,
isLoading,
refetch: mutate,
error: error
};
}

View File

@@ -1,36 +0,0 @@
import { supabase } from "@/lib/supabase/client";
import { useQuery } from "@supabase-cache-helpers/postgrest-swr";
export function useRelations(individualId: string | null | undefined) {
const { data: individuals, mutate, isLoading, error } = useQuery(
Boolean(individualId)
? supabase
.from("relationships")
.select(`
id,
individual_a(id, full_name, image_url),
individual_b(id, full_name, image_url),
relation_type,
confidence_level
`)
.or(`individual_a.eq.${individualId},individual_b.eq.${individualId}`)
: null,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
const relations = individuals
? individuals.map((rel: { individual_a: any, individual_b: any, relation_type: string }) => {
return rel.individual_a?.id === individualId
? { ...rel.individual_b, relation_type: rel.relation_type }
: { ...rel.individual_a, relation_type: rel.relation_type }
}).filter(Boolean)
: [];
return {
relations,
isLoading,
refetch: mutate,
error
};
}

View File

@@ -1,34 +0,0 @@
import { supabase } from "@/lib/supabase/client";
import { useQuery } from "@supabase-cache-helpers/postgrest-swr";
export function useInvestigations(project_id: string) {
const { data: investigations, count, mutate, isLoading, error } = useQuery(
supabase
.from('investigations')
.select('id, title, description')
.eq("project_id", project_id)
.order("last_updated_at", { ascending: false }),
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
return { investigations, count, isLoading, refetch: mutate, error };
}
export function useInvestigation(investigationId: string | string[] | undefined) {
if (!investigationId) return { investigation: null }
const { data: investigation, mutate, isLoading, error } = useQuery(
supabase
.from('investigations')
.select('id, title, description')
.eq("id", investigationId)
.single(),
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
return { investigation: investigation ?? null, isLoading, refetch: mutate, error };
}

View File

@@ -1,25 +0,0 @@
import { supabase } from "@/lib/supabase/client";
import { useQuery } from "@supabase-cache-helpers/postgrest-swr";
export function useEmails(investigationId: string | null | undefined) {
const { data: emails, mutate, isLoading, error } = useQuery(Boolean(investigationId) ?
supabase
.from('emails')
.select(`
*
`)
.eq("investigation_id", investigationId)
: null,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
return {
emails: emails || null,
isLoading,
refetch: mutate,
error: error
};
}

View File

@@ -1,25 +0,0 @@
import { supabase } from "@/lib/supabase/client";
import { useQuery } from "@supabase-cache-helpers/postgrest-swr";
export function useIndividuals(investigationId: string | null | undefined) {
const { data: individuals, mutate, isLoading, error } = useQuery(Boolean(investigationId) ?
supabase
.from('individuals')
.select(`
*
`)
.eq("investigation_id", investigationId)
: null,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
return {
individuals: individuals || null,
isLoading,
refetch: mutate,
error: error
};
}

View File

@@ -1,25 +0,0 @@
import { supabase } from "@/lib/supabase/client";
import { useQuery } from "@supabase-cache-helpers/postgrest-swr";
export function usePhones(investigationId: string | null | undefined) {
const { data: phones, mutate, isLoading, error } = useQuery(Boolean(investigationId) ?
supabase
.from('phone_numbers')
.select(`
*
`)
.eq("investigation_id", investigationId)
: null,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
return {
phones: phones || null,
isLoading,
refetch: mutate,
error: error
};
}

View File

@@ -1,19 +0,0 @@
import { supabase } from "@/lib/supabase/client";
import { useQuery } from "@supabase-cache-helpers/postgrest-swr";
export function useSearchResults(search: string | undefined, investigationId: string | string[] | undefined) {
const { data: results, count, mutate, isLoading, error } = useQuery(search ?
supabase
.from('individuals')
.select('*')
.eq("investigation_id", investigationId)
.ilike('full_name', `%${search}%`) : null,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
if (error) throw error
return { results, count, isLoading, refetch: mutate, error };
}

View File

@@ -1,25 +0,0 @@
import { supabase } from "@/lib/supabase/client";
import { useQuery } from "@supabase-cache-helpers/postgrest-swr";
export function useSocials(investigationId: string | null | undefined) {
const { data: socials, mutate, isLoading, error } = useQuery(Boolean(investigationId) ?
supabase
.from('social_accounts')
.select(`
*
`)
.eq("investigation_id", investigationId)
: null,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
return {
socials: socials || null,
isLoading,
refetch: mutate,
error: error
};
}

View File

@@ -1,21 +0,0 @@
import { supabase } from "@/lib/supabase/client";
import { useQuery } from "@supabase-cache-helpers/postgrest-swr";
export function useUsernames(investigationId: string | string[] | undefined) {
if (!investigationId) return { usernames: [] }
const { data: usernames, count, mutate, isLoading, error } = useQuery(
supabase
.from('social_accounts')
.select('id, username')
.eq("investigation_id", investigationId)
.not('username', 'is', null)
.neq('username', ''),
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
if (error) throw error
return { usernames, count, isLoading, refetch: mutate, error };
}

View File

@@ -1,42 +0,0 @@
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
export default function useLocalStorage<T>(key: string, initialValue: T): [T, Dispatch<SetStateAction<T>>] {
const [storedValue, setStoredValue] = useState(initialValue);
const [firstLoadDone, setFirstLoadDone] = useState(false);
useEffect(() => {
const fromLocal = () => {
if (typeof window === 'undefined') {
return initialValue;
}
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) as T : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
};
// Only set stored value if it hasn't been set yet (on initial load)
if (!firstLoadDone) {
setStoredValue(fromLocal());
setFirstLoadDone(true);
}
}, [firstLoadDone, initialValue, key]); // Only run when first load is done, initialValue, or key change
useEffect(() => {
if (!firstLoadDone) return;
try {
if (typeof window !== 'undefined') {
window.localStorage.setItem(key, JSON.stringify(storedValue));
}
} catch (error) {
console.log(error);
}
}, [storedValue, firstLoadDone, key]); // Update localStorage when storedValue changes
return [storedValue, setStoredValue];
}

View File

@@ -1,27 +0,0 @@
import { useMemo } from "react"
import { CameraIcon, FacebookIcon, GithubIcon, InstagramIcon, MessageCircleDashedIcon, SendIcon } from 'lucide-react';
export const usePlatformIcons = (size = "small") => {
const className = size === "small" ? 'h-3 w-3' : 'h-5 w-5'
const platformsIcons = useMemo(() => ({
"facebook": {
icon: <FacebookIcon className={className} />, color: "indigo"
},
"instagram": {
icon: <InstagramIcon className={className} />, color: "plum"
},
"telegram": {
icon: <SendIcon className={className} />, color: "cyan"
},
"signal": {
icon: <MessageCircleDashedIcon className={className} />, color: "blue"
},
"snapchat": {
icon: <CameraIcon className={className} />, color: "yellow"
},
"github": {
icon: <GithubIcon className={className} />, color: "gray"
},
}), [])
return platformsIcons
}

View File

@@ -1,7 +0,0 @@
import { createBrowserClient } from '@supabase/ssr'
if (!process.env.NEXT_PUBLIC_SUPABASE_URL || !process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
throw new Error('Missing Supabase environment variables');
}
export const supabase = createBrowserClient(process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY)

View File

@@ -1,65 +0,0 @@
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function updateSession(request: NextRequest) {
let supabaseResponse = NextResponse.next({
request,
})
const supabase = createServerClient(
process.env.NEXT_PUBLIC_DOCKER_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))
supabaseResponse = NextResponse.next({
request,
})
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
// 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()
if (
!user &&
!request.nextUrl.pathname.startsWith('/login') &&
!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()
url.pathname = '/login'
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:
// 1. Pass the request in it, like so:
// const myNewResponse = NextResponse.next({ request })
// 2. Copy over the cookies, like so:
// myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
// 3. Change the myNewResponse object to fit your needs, but avoid changing
// the cookies!
// 4. Finally:
// return myNewResponse
// If this is not done, you may be causing the browser and server to go out
// of sync and terminate the user's session prematurely!
return supabaseResponse
}

View File

@@ -1,43 +0,0 @@
import { createServerClient } from '@supabase/ssr'
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_DOCKER_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()
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
} catch {
// The `setAll` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
},
}
)
}

View File

@@ -1,260 +0,0 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
//@ts-ignore
import * as d3 from "d3-force"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export const zoomSelector = (s: { transform: number[]; }) => s.transform[2] >= 0.6;
import { Position, MarkerType } from '@xyflow/react';
import { AppNode } from "@/store/flow-store";
// this helper function returns the intersection point
// of the line between the center of the intersectionNode and the target node
function getNodeIntersection(intersectionNode: { measured: { width: any; height: any; }; internals: { positionAbsolute: any; }; }, targetNode: { internals: { positionAbsolute: any; }; measured: { width: number; height: number; }; }) {
// https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a
const { width: intersectionNodeWidth, height: intersectionNodeHeight } =
intersectionNode.measured;
const intersectionNodePosition = intersectionNode.internals.positionAbsolute;
const targetPosition = targetNode.internals.positionAbsolute;
const w = intersectionNodeWidth / 2;
const h = intersectionNodeHeight / 2;
const x2 = intersectionNodePosition.x + w;
const y2 = intersectionNodePosition.y + h;
const x1 = targetPosition.x + targetNode.measured.width / 2;
const y1 = targetPosition.y + targetNode.measured.height / 2;
const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h);
const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h);
const a = 1 / (Math.abs(xx1) + Math.abs(yy1));
const xx3 = a * xx1;
const yy3 = a * yy1;
const x = w * (xx3 + yy3) + x2;
const y = h * (-xx3 + yy3) + y2;
return { x, y };
}
// returns the position (top,right,bottom or right) passed node compared to the intersection point
function getEdgePosition(node: { internals: { positionAbsolute: any; }; }, intersectionPoint: { x: any; y: any; }) {
const n = { ...node.internals.positionAbsolute, ...node };
const nx = Math.round(n.x);
const ny = Math.round(n.y);
const px = Math.round(intersectionPoint.x);
const py = Math.round(intersectionPoint.y);
if (px <= nx + 1) {
return Position.Left;
}
if (px >= nx + n.measured.width - 1) {
return Position.Right;
}
if (py <= ny + 1) {
return Position.Top;
}
if (py >= n.y + n.measured.height - 1) {
return Position.Bottom;
}
return Position.Top;
}
// returns the parameters (sx, sy, tx, ty, sourcePos, targetPos) you need to create an edge
export function getEdgeParams(source: any, target: any) {
const sourceIntersectionPoint = getNodeIntersection(source, target);
const targetIntersectionPoint = getNodeIntersection(target, source);
const sourcePos = getEdgePosition(source, sourceIntersectionPoint);
const targetPos = getEdgePosition(target, targetIntersectionPoint);
return {
sx: sourceIntersectionPoint.x,
sy: sourceIntersectionPoint.y,
tx: targetIntersectionPoint.x,
ty: targetIntersectionPoint.y,
sourcePos,
targetPos,
};
}
export function initialElements() {
const nodes = [];
const edges = [];
const center = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
nodes.push({ id: 'target', data: { label: 'Target' }, position: center });
for (let i = 0; i < 8; i++) {
const degrees = i * (360 / 8);
const radians = degrees * (Math.PI / 180);
const x = 250 * Math.cos(radians) + center.x;
const y = 250 * Math.sin(radians) + center.y;
nodes.push({ id: `${i}`, data: { label: 'Source' }, position: { x, y } });
edges.push({
id: `edge-${i}`,
target: 'target',
source: `${i}`,
type: 'floating',
markerEnd: {
type: MarkerType.Arrow,
},
});
}
return { nodes, edges };
}
interface Node {
id: string
position?: { x: number; y: number }
measured?: { width: number; height: number }
[key: string]: any
}
interface Edge {
source: string
target: string
[key: string]: any
}
interface LayoutOptions {
direction?: string
strength?: number
distance?: number
iterations?: number
}
export const getLayoutedElements = (
nodes: AppNode[],
edges: Edge[],
options: LayoutOptions = {
direction: "LR",
strength: -300,
distance: 100,
iterations: 300,
},
) => {
// Create a map of node IDs to indices for the simulation
const nodeMap = new Map(nodes.map((node, i) => [node.id, i]))
// Create a copy of nodes with positions for the simulation
const nodesCopy = nodes.map((node) => ({
...node,
x: node.position?.x || Math.random() * 500,
y: node.position?.y || Math.random() * 500,
width: node.measured?.width || 0,
height: node.measured?.height || 0,
}))
// Create links for the simulation using indices
const links = edges.map((edge) => ({
source: nodeMap.get(edge.source),
target: nodeMap.get(edge.target),
original: edge,
}))
// Create the simulation
const simulation = d3
.forceSimulation(nodesCopy)
.force(
"link",
d3.forceLink(links).id((d: any) => nodeMap.get(d.id)),
)
.force("charge", d3.forceManyBody().strength(options.strength || -300))
.force("center", d3.forceCenter(250, 250))
.force(
"collision",
d3.forceCollide().radius((d: any) => Math.max(d.width, d.height) / 2 + 10),
)
// If direction is horizontal, adjust forces
if (options.direction === "LR") {
simulation.force("x", d3.forceX(250).strength(0.1))
simulation.force("y", d3.forceY(250).strength(0.05))
} else {
simulation.force("x", d3.forceX(250).strength(0.05))
simulation.force("y", d3.forceY(250).strength(0.1))
}
// Run the simulation synchronously
simulation.stop()
for (let i = 0; i < (options.iterations || 300); i++) {
simulation.tick()
}
// Update node positions based on simulation results
const updatedNodes = nodesCopy.map((node) => ({
...node,
position: {
x: node.x - node.width / 2,
y: node.y - node.height / 2,
},
}))
return {
nodes: updatedNodes,
edges,
}
}
export const sanitize = (name: string) => {
return name
.normalize("NFKD") // Decompose special characters (e.g., accents)
.replace(/[\u0300-\u036f]/g, "") // Remove accent marks
.replace(/[^a-zA-Z0-9.\-_]/g, "_") // Replace invalid characters with underscores
.replace(/_{2,}/g, "_") // Replace multiple underscores with a single one
.replace(/^_|_$/g, "") // Trim leading or trailing underscores
.toLowerCase(); // Convert to lowercase for consistency
};
export const formatFileSize = (bytes: number) => {
if (bytes < 1024) return bytes + " B"
else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + " KB"
else return (bytes / 1048576).toFixed(1) + " MB"
}
export const nodesTypes = {
emails: { table: "emails", type: "email", fields: ["email"] },
individuals: { table: "individuals", type: "individual", fields: ["full_name"] },
phone_numbers: { table: "phone_numbers", type: "phone", fields: ["phone_number"] },
ip_addresses: { table: "ip_addresses", type: "ip", fields: ["ip_address"] },
social_accounts_facebook: {
table: "social_accounts",
type: "social",
fields: ["profile_url", "username", "platform:facebook"],
},
social_accounts_instagram: {
table: "social_accounts",
type: "social",
fields: ["profile_url", "username", "platform:instagram"],
},
social_accounts_telegram: {
table: "social_accounts",
type: "social",
fields: ["profile_url", "username", "platform:telegram"],
},
social_accounts_snapchat: {
table: "social_accounts",
type: "social",
fields: ["profile_url", "username", "platform:snapchat"],
},
social_accounts_signal: {
table: "social_accounts",
type: "social",
fields: ["profile_url", "username", "platform:signal"],
},
social_accounts_github: {
table: "social_accounts",
type: "social",
fields: ["profile_url", "username", "platform:github"],
},
physical_addresses: { table: "physical_addresses", type: "address", fields: ["address", "city", "country", "zip"] },
}