six-degrees 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +3 -0
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +14 -14
- package/.next/standalone/.next/routes-manifest.json +18 -0
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +26 -18
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +26 -18
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +25 -16
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/admin-delete/route.js +2 -2
- package/.next/standalone/.next/server/app/api/admin-delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/admin-update/route.js +2 -2
- package/.next/standalone/.next/server/app/api/admin-update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bridges/route.js +2 -2
- package/.next/standalone/.next/server/app/api/bridges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk-import/route.js +2 -2
- package/.next/standalone/.next/server/app/api/bulk-import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js +3 -3
- package/.next/standalone/.next/server/app/api/company-scores/legacy/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/company-scores/route.js +3 -3
- package/.next/standalone/.next/server/app/api/company-scores/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/connections/route.js +2 -2
- package/.next/standalone/.next/server/app/api/connections/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/backup/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/data/backup/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/data/backup/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/data/backup/route.js +7 -0
- package/.next/standalone/.next/server/app/api/data/backup/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/data/backup/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/data/backup/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/data/csv/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/data/csv/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/data/csv/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/data/csv/route.js +8 -0
- package/.next/standalone/.next/server/app/api/data/csv/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/data/csv/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/data/csv/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/data/export/route.js +1 -1
- package/.next/standalone/.next/server/app/api/data/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js +1 -1
- package/.next/standalone/.next/server/app/api/data/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js +1 -1
- package/.next/standalone/.next/server/app/api/data/restart/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/restore/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/data/restore/route/build-manifest.json +12 -0
- package/.next/standalone/.next/server/app/api/data/restore/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/data/restore/route.js +7 -0
- package/.next/standalone/.next/server/app/api/data/restore/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/data/restore/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/data/restore/route_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app/api/data/reveal/route.js +1 -1
- package/.next/standalone/.next/server/app/api/data/reveal/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js +1 -1
- package/.next/standalone/.next/server/app/api/data/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js +2 -2
- package/.next/standalone/.next/server/app/api/delete-cluster/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/ingest/route.js +2 -2
- package/.next/standalone/.next/server/app/api/ingest/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/route.js +2 -2
- package/.next/standalone/.next/server/app/api/network/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/.next/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/outreach/route.js +2 -2
- package/.next/standalone/.next/server/app/api/outreach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/queue/route.js +2 -2
- package/.next/standalone/.next/server/app/api/queue/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/scraper/route.js +4 -2
- package/.next/standalone/.next/server/app/api/scraper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js +3 -3
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js +3 -3
- package/.next/standalone/.next/server/app/api/settings/sector-preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js +3 -3
- package/.next/standalone/.next/server/app/api/settings/sector-suggestions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/setup-profile/route.js +2 -2
- package/.next/standalone/.next/server/app/api/setup-profile/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/crm/route.js +1 -1
- package/.next/standalone/.next/server/app/api/social/crm/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/messages/route.js +1 -1
- package/.next/standalone/.next/server/app/api/social/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/social/route.js +2 -2
- package/.next/standalone/.next/server/app/api/social/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/unlock/route.js +2 -2
- package/.next/standalone/.next/server/app/api/unlock/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/update-images/route.js +2 -2
- package/.next/standalone/.next/server/app/api/update-images/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/users/route.js +2 -2
- package/.next/standalone/.next/server/app/api/users/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js +1 -1
- package/.next/standalone/.next/server/app/avatars/[file]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page.js +1 -1
- package/.next/standalone/.next/server/app/import/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/import/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/import.html +1 -1
- package/.next/standalone/.next/server/app/import.rsc +32 -24
- package/.next/standalone/.next/server/app/import.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/import.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/import.segments/import/__PAGE__.segment.rsc +23 -18
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +29 -22
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +20 -15
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +29 -22
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch/page.js +1 -1
- package/.next/standalone/.next/server/app/launch/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/launch/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/launch.html +2 -2
- package/.next/standalone/.next/server/app/launch.rsc +32 -24
- package/.next/standalone/.next/server/app/launch.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/launch.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/launch.segments/launch/__PAGE__.segment.rsc +23 -18
- package/.next/standalone/.next/server/app/page.js +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js +1 -1
- package/.next/standalone/.next/server/app/paths/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/paths/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/paths.html +1 -1
- package/.next/standalone/.next/server/app/paths.rsc +32 -24
- package/.next/standalone/.next/server/app/paths.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/paths.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/paths.segments/paths/__PAGE__.segment.rsc +21 -18
- package/.next/standalone/.next/server/app/profile/page.js +1 -1
- package/.next/standalone/.next/server/app/profile/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/profile/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/profile.html +1 -1
- package/.next/standalone/.next/server/app/profile.rsc +32 -24
- package/.next/standalone/.next/server/app/profile.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/profile.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/profile.segments/profile/__PAGE__.segment.rsc +23 -18
- package/.next/standalone/.next/server/app/queue/page.js +1 -1
- package/.next/standalone/.next/server/app/queue/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/queue/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/queue.html +1 -1
- package/.next/standalone/.next/server/app/queue.rsc +32 -24
- package/.next/standalone/.next/server/app/queue.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/queue.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/queue.segments/queue/__PAGE__.segment.rsc +20 -18
- package/.next/standalone/.next/server/app/scores/page.js +1 -1
- package/.next/standalone/.next/server/app/scores/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/scores/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/scores.html +1 -1
- package/.next/standalone/.next/server/app/scores.rsc +32 -24
- package/.next/standalone/.next/server/app/scores.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/scores.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/scores.segments/scores/__PAGE__.segment.rsc +23 -18
- package/.next/standalone/.next/server/app/settings/page.js +1 -1
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +32 -24
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +20 -18
- package/.next/standalone/.next/server/app/setup/page.js +1 -1
- package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/setup.html +1 -1
- package/.next/standalone/.next/server/app/setup.rsc +32 -24
- package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +21 -18
- package/.next/standalone/.next/server/app/social/page.js +1 -1
- package/.next/standalone/.next/server/app/social/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/social/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/social.html +1 -1
- package/.next/standalone/.next/server/app/social.rsc +32 -24
- package/.next/standalone/.next/server/app/social.segments/_full.segment.rsc +32 -24
- package/.next/standalone/.next/server/app/social.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/social.segments/social/__PAGE__.segment.rsc +23 -18
- package/.next/standalone/.next/server/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00la_y0._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__05_3-22._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__15wc9e-._.js → [root-of-the-server]__063s0x7._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__066rab1._.js +7 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__07t47cm._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__13dx4sd._.js → [root-of-the-server]__0_4y8z7._.js} +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cj5jqc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e0fei3._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0fooke1._.js +7 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljnyho._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o5-tcg._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0qrnous._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t3v1a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0t6y79e._.js +7 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wmexuc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0x29muh._.js +7 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xu34sz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__133knx1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__15-17hl._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19b7mig._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1a_6x37._.js +8 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1an_8pa._.js +7 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bbcwub._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ducpz2._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1eris29._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1gjo_cq._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qcm08_._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tir0vc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1w25q0m._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xhyinn._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20rwfdw._.js +1 -1
- package/.next/standalone/.next/server/chunks/_02twlca._.js +1 -1
- package/.next/standalone/.next/server/chunks/_072jl0y._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0lw8_i0._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0sqqpzb._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1_fynia._.js +7 -7
- package/.next/standalone/.next/server/chunks/_1fn30_l._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1itd1o_._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1iwkh8t._.js +1 -1
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_backup_route_actions_0zoj04p.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_csv_route_actions_0l6m7lg.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_data_restore_route_actions_0-b05bv.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_02iz_9q._.js +4 -0
- package/.next/standalone/.next/server/chunks/lib_05agvk3._.js +4 -0
- package/.next/standalone/.next/server/chunks/lib_0v84u66._.js +4 -0
- package/.next/standalone/.next/server/chunks/lib_db-client_0_dch35.js +174 -0
- package/.next/standalone/.next/server/chunks/lib_linkedin-export_12imqxg.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_rpc_005abye.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0b17r6v.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0c9d9ti.js +7 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0doq0t-.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0fd2rlw.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0is4qoc.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0jnfp2e.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0nef_n7.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_0plrzra.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_13a4m0c.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1if17c-.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1j-zw7v.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1sngt-q.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1v2sgme.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1vh-tgx.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_rpc_1zaubz7.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qd8pzm._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qdeuyo._.js → [root-of-the-server]__1-012q1._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_01o3i5y._.js +7 -0
- package/.next/standalone/.next/server/chunks/ssr/_02sband._.js +7 -0
- package/.next/standalone/.next/server/chunks/ssr/_06rgzfh._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_07zb7lu._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0_79idq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0gy11ar._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0hb-dhy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_14nxd3q._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_189g8lg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1f_n00p._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1fm1ih_._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1k2nmbw._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1k9qfsk._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_141w189._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_0t908t-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_0uuff77._.js +25 -2
- package/.next/standalone/.next/server/chunks/ssr/app_components_1j3q-h5._.js +25 -2
- package/.next/standalone/.next/server/chunks/ssr/app_components_CompanyScores_1g0mytz.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_import_page_1fmu6_l.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_launch_page_19t0dyd.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_page_1fazqbf.js +25 -13
- package/.next/standalone/.next/server/chunks/ssr/app_paths_page_1ol90m7.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_profile_page_0u3-l5y.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_queue_page_0c6znd6.js +11 -11
- package/.next/standalone/.next/server/chunks/ssr/app_settings_page_1f8mjj_.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_setup_page_1-282zx.js +12 -13
- package/.next/standalone/.next/server/chunks/ssr/app_social_page_0h7zxda.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{lib_1c4ieuw._.js → lib_csv_1ttiu_h.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_galaxy-lab_108_lu-.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_separation_1305zsx.js +3 -0
- package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__0pco7a6._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/03-_esd2x_yk6.js +1 -0
- package/.next/standalone/.next/static/chunks/03-demj2iy-0p.js +44 -0
- package/.next/standalone/.next/static/chunks/0_1r-3csplxnj.js +1 -0
- package/.next/standalone/.next/static/chunks/0an_b6zi1r_qq.js +5 -0
- package/.next/standalone/.next/static/chunks/0e3g0sil79cus.js +21 -0
- package/.next/standalone/.next/static/chunks/0kscsqnvfq3-r.js +1 -0
- package/.next/standalone/.next/static/chunks/0p3g5tfostsep.js +7 -0
- package/.next/standalone/.next/static/chunks/0zd0t7e60bn4z.js +1 -0
- package/.next/standalone/.next/static/chunks/163b-20vwa06i.js +34 -0
- package/.next/standalone/.next/static/chunks/{0ws7zy0w57ufi.js → 1eyajb9ntzlrl.js} +1 -1
- package/.next/standalone/.next/static/chunks/1pa-2v1ajvrko.js +1 -0
- package/.next/standalone/.next/static/chunks/1qbjermbf68xj.js +1 -0
- package/.next/standalone/.next/static/chunks/1z9lb-dbcg4aa.js +1 -0
- package/.next/standalone/.next/static/chunks/23_1z47al92nz.js +57 -0
- package/.next/standalone/.next/static/chunks/260bomtr214m8.js +34 -0
- package/.next/standalone/.next/static/chunks/2eo9pqq9gay3r.js +34 -0
- package/.next/standalone/.next/static/chunks/2xk2ra4piiuod.js +34 -0
- package/.next/standalone/.next/static/chunks/2z1-q0_vunbql.js +2 -0
- package/.next/standalone/.next/static/chunks/2zf4jmz9l3li2.js +1 -0
- package/.next/standalone/.next/static/chunks/34g73qhayuevu.js +1 -0
- package/.next/standalone/.next/static/chunks/3b0-14cxqsprt.js +1 -0
- package/.next/standalone/.next/static/chunks/3jarus1_38s2z.js +1 -0
- package/.next/standalone/.next/static/chunks/3ssu341hav_sq.css +3 -0
- package/.next/standalone/.next/static/chunks/3v1qd3sqhqn0d.js +1 -0
- package/.next/standalone/.next/static/chunks/43pg2sursiypz.js +1 -0
- package/.next/standalone/.next/static/chunks/44yn0tcd9dfk3.js +1 -0
- package/.next/standalone/CHANGELOG.md +143 -0
- package/.next/standalone/README.md +71 -49
- package/.next/standalone/SECURITY.md +17 -11
- package/.next/standalone/app/api/data/backup/route.js +25 -0
- package/.next/standalone/app/api/data/csv/route.js +74 -0
- package/.next/standalone/app/api/data/export/route.js +1 -1
- package/.next/standalone/app/api/data/restore/route.js +48 -0
- package/.next/standalone/app/api/data/reveal/route.js +19 -6
- package/.next/standalone/app/api/data/route.js +15 -4
- package/.next/standalone/app/api/ingest/route.js +1 -1
- package/.next/standalone/app/api/queue/route.js +1 -1
- package/.next/standalone/app/api/scraper/route.js +120 -32
- package/.next/standalone/app/api/social/crm/route.js +2 -1
- package/.next/standalone/app/components/AppHeader.js +20 -16
- package/.next/standalone/app/components/AppTabs.js +8 -8
- package/.next/standalone/app/components/AutoScanButton.js +2 -2
- package/.next/standalone/app/components/Avatar.js +1 -1
- package/.next/standalone/app/components/ChainView.js +184 -112
- package/.next/standalone/app/components/CirclePanel.js +16 -16
- package/.next/standalone/app/components/ClusterSpinner.js +64 -0
- package/.next/standalone/app/components/CompanyScores.js +20 -20
- package/.next/standalone/app/components/EmptyState.js +39 -18
- package/.next/standalone/app/components/FieldStep.js +15 -13
- package/.next/standalone/app/components/FilterPanel.js +17 -17
- package/.next/standalone/app/components/ForceGraph.js +49 -14
- package/.next/standalone/app/components/GalaxyLab.js +33 -33
- package/.next/standalone/app/components/GridView.js +18 -18
- package/.next/standalone/app/components/LegacyScoresCard.js +10 -11
- package/.next/standalone/app/components/LinkedInLimits.js +24 -19
- package/.next/standalone/app/components/LiquidGlass.js +99 -0
- package/.next/standalone/app/components/ListView.js +20 -20
- package/.next/standalone/app/components/MapControls.js +5 -5
- package/.next/standalone/app/components/MappingProgress.js +16 -16
- package/.next/standalone/app/components/NoteDetail.js +13 -13
- package/.next/standalone/app/components/OnboardingGate.js +4 -4
- package/.next/standalone/app/components/OutlinkQuest.js +25 -25
- package/.next/standalone/app/components/PathsAnalyzer.js +37 -36
- package/.next/standalone/app/components/PeopleMap.js +21 -21
- package/.next/standalone/app/components/RingsView.js +11 -11
- package/.next/standalone/app/components/ScanRadar.js +84 -39
- package/.next/standalone/app/components/ScanStatusBar.js +22 -18
- package/.next/standalone/app/components/ScanTrail.js +8 -5
- package/.next/standalone/app/components/SeparationView.js +168 -104
- package/.next/standalone/app/components/Sidebar.js +161 -161
- package/.next/standalone/app/components/StaleServerBanner.js +4 -4
- package/.next/standalone/app/components/TheirCircle.js +26 -26
- package/.next/standalone/app/components/ThemeDefs.js +42 -0
- package/.next/standalone/app/components/ThemeLoader.js +20 -1
- package/.next/standalone/app/components/UpdatePanel.js +22 -22
- package/.next/standalone/app/components/UserProvider.js +22 -15
- package/.next/standalone/app/components/settings/AppearanceSection.js +47 -24
- package/.next/standalone/app/components/settings/DataSection.js +276 -109
- package/.next/standalone/app/components/settings/NetworkHealthSection.js +13 -13
- package/.next/standalone/app/components/settings/SectorPicker.js +12 -12
- package/.next/standalone/app/components/settings/SectorSection.js +13 -14
- package/.next/standalone/app/components/settings/TierSection.js +9 -9
- package/.next/standalone/app/components/settings/TitleSection.js +11 -11
- package/.next/standalone/app/components/settings/UsageSection.js +313 -0
- package/.next/standalone/app/components/ui.js +8 -8
- package/.next/standalone/app/globals.css +159 -2
- package/.next/standalone/app/import/page.js +94 -38
- package/.next/standalone/app/launch/page.js +5 -5
- package/.next/standalone/app/layout.js +6 -2
- package/.next/standalone/app/page.js +53 -37
- package/.next/standalone/app/paths/page.js +63 -63
- package/.next/standalone/app/profile/page.js +58 -58
- package/.next/standalone/app/queue/page.js +54 -54
- package/.next/standalone/app/scores/page.js +2 -2
- package/.next/standalone/app/settings/page.js +16 -13
- package/.next/standalone/app/setup/page.js +221 -125
- package/.next/standalone/app/social/Crm.js +63 -62
- package/.next/standalone/app/social/SocialHub.js +22 -22
- package/.next/standalone/app/social/Thread.js +7 -7
- package/.next/standalone/app/social/page.js +2 -2
- package/.next/standalone/lib/backups.js +458 -0
- package/.next/standalone/lib/chain-layout.js +32 -0
- package/.next/standalone/lib/csv-store.js +131 -0
- package/.next/standalone/lib/csv.js +137 -39
- package/.next/standalone/lib/data-folder.js +19 -3
- package/.next/standalone/lib/data-import.js +5 -2
- package/.next/standalone/lib/db-client.js +78 -20
- package/.next/standalone/lib/demo.js +5 -4
- package/.next/standalone/lib/galaxy-export.js +9 -2
- package/.next/standalone/lib/galaxy-lab.js +19 -5
- package/.next/standalone/lib/galaxy-layouts.js +43 -0
- package/.next/standalone/lib/gate.js +10 -4
- package/.next/standalone/lib/ingest.js +1 -1
- package/.next/standalone/lib/linkedin-limits.js +212 -11
- package/.next/standalone/lib/liquid-glass.js +59 -0
- package/.next/standalone/lib/notifications.js +13 -0
- package/.next/standalone/lib/scan-pace.js +11 -0
- package/.next/standalone/lib/scan-progress.js +20 -0
- package/.next/standalone/lib/scan-risk.js +44 -0
- package/.next/standalone/lib/scanner-setup.js +97 -15
- package/.next/standalone/lib/scraper-client.js +17 -3
- package/.next/standalone/lib/search-risk.js +7 -3
- package/.next/standalone/lib/settings-client.js +5 -3
- package/.next/standalone/lib/settings.js +10 -0
- package/.next/standalone/lib/social-store.js +3 -2
- package/.next/standalone/lib/synced-setting.js +46 -0
- package/.next/standalone/lib/theme-store.js +42 -10
- package/.next/standalone/lib/themes.js +111 -21
- package/.next/standalone/lib/usage.js +195 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/scripts/scrape.py +26 -2
- package/.next/static/chunks/03-_esd2x_yk6.js +1 -0
- package/.next/static/chunks/03-demj2iy-0p.js +44 -0
- package/.next/static/chunks/0_1r-3csplxnj.js +1 -0
- package/.next/static/chunks/0an_b6zi1r_qq.js +5 -0
- package/.next/static/chunks/0e3g0sil79cus.js +21 -0
- package/.next/static/chunks/0kscsqnvfq3-r.js +1 -0
- package/.next/static/chunks/0p3g5tfostsep.js +7 -0
- package/.next/static/chunks/0zd0t7e60bn4z.js +1 -0
- package/.next/static/chunks/163b-20vwa06i.js +34 -0
- package/.next/static/chunks/{0ws7zy0w57ufi.js → 1eyajb9ntzlrl.js} +1 -1
- package/.next/static/chunks/1pa-2v1ajvrko.js +1 -0
- package/.next/static/chunks/1qbjermbf68xj.js +1 -0
- package/.next/static/chunks/1z9lb-dbcg4aa.js +1 -0
- package/.next/static/chunks/23_1z47al92nz.js +57 -0
- package/.next/static/chunks/260bomtr214m8.js +34 -0
- package/.next/static/chunks/2eo9pqq9gay3r.js +34 -0
- package/.next/static/chunks/2xk2ra4piiuod.js +34 -0
- package/.next/static/chunks/2z1-q0_vunbql.js +2 -0
- package/.next/static/chunks/2zf4jmz9l3li2.js +1 -0
- package/.next/static/chunks/34g73qhayuevu.js +1 -0
- package/.next/static/chunks/3b0-14cxqsprt.js +1 -0
- package/.next/static/chunks/3jarus1_38s2z.js +1 -0
- package/.next/static/chunks/3ssu341hav_sq.css +3 -0
- package/.next/static/chunks/3v1qd3sqhqn0d.js +1 -0
- package/.next/static/chunks/43pg2sursiypz.js +1 -0
- package/.next/static/chunks/44yn0tcd9dfk3.js +1 -0
- package/CHANGELOG.md +143 -0
- package/README.md +71 -49
- package/SECURITY.md +17 -11
- package/lib/backups.js +458 -0
- package/lib/chain-layout.js +32 -0
- package/lib/csv-store.js +131 -0
- package/lib/csv.js +137 -39
- package/lib/data-folder.js +19 -3
- package/lib/data-import.js +5 -2
- package/lib/db-client.js +78 -20
- package/lib/demo.js +5 -4
- package/lib/galaxy-export.js +9 -2
- package/lib/galaxy-lab.js +19 -5
- package/lib/galaxy-layouts.js +43 -0
- package/lib/gate.js +10 -4
- package/lib/ingest.js +1 -1
- package/lib/linkedin-limits.js +212 -11
- package/lib/liquid-glass.js +59 -0
- package/lib/notifications.js +13 -0
- package/lib/scan-pace.js +11 -0
- package/lib/scan-progress.js +20 -0
- package/lib/scan-risk.js +44 -0
- package/lib/scanner-setup.js +97 -15
- package/lib/scraper-client.js +17 -3
- package/lib/search-risk.js +7 -3
- package/lib/settings-client.js +5 -3
- package/lib/settings.js +10 -0
- package/lib/social-store.js +3 -2
- package/lib/synced-setting.js +46 -0
- package/lib/theme-store.js +42 -10
- package/lib/themes.js +111 -21
- package/lib/usage.js +195 -0
- package/package.json +1 -1
- package/scripts/scrape.py +26 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-rnla2._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__14ou7j5._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16iu0hv._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1kbl21f._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rphsdk._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_0851sk0._.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_1q7p2ro._.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_1r6l86l._.js +0 -8
- package/.next/standalone/.next/server/chunks/lib_db-client_15le7cw.js +0 -174
- package/.next/standalone/.next/server/chunks/lib_rpc_04s8mzr.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_053z7as.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0b_-2te.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0dhbxdm.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0dxlk31.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0f8b-qc.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0pzie7_.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0r76fq_.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_0ut7pjp.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_15zyqaz.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_18zpjtq.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1a8xkiz.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1e_55xo.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1oxfd1_.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_rpc_1s0lod0.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08oxiqh._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_03l7vge._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_08g31gm._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0gk_ofx._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0nwsf93._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0vho1lb._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1-658fq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1bc46kz._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_2105jz8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_components_settings_SectorPicker_1c-29qw.js +0 -3
- package/.next/standalone/.next/static/chunks/02pq76u7y4v9-.js +0 -22
- package/.next/standalone/.next/static/chunks/0cmq0exd4d0h9.js +0 -1
- package/.next/standalone/.next/static/chunks/0i02yxppzds1o.js +0 -1
- package/.next/standalone/.next/static/chunks/0m_p4k-q2qh--.js +0 -1
- package/.next/standalone/.next/static/chunks/0oyslj62jkubd.js +0 -1
- package/.next/standalone/.next/static/chunks/0vo-l8hxneba6.css +0 -3
- package/.next/standalone/.next/static/chunks/0yh8z2tbrc5i_.js +0 -1
- package/.next/standalone/.next/static/chunks/17-rhp8sg4m1b.js +0 -44
- package/.next/standalone/.next/static/chunks/172e10mi-tpo_.js +0 -34
- package/.next/standalone/.next/static/chunks/1ei5vbe2uw6fl.js +0 -1
- package/.next/standalone/.next/static/chunks/1f7_pl_wq7lh3.js +0 -11
- package/.next/standalone/.next/static/chunks/1jzbe7zdx9vbj.js +0 -2
- package/.next/standalone/.next/static/chunks/1m3017gp790pw.js +0 -45
- package/.next/standalone/.next/static/chunks/1t20e8u0st7ep.js +0 -1
- package/.next/standalone/.next/static/chunks/2-ch9rm27eril.js +0 -11
- package/.next/standalone/.next/static/chunks/2ldv-v3s9yaif.js +0 -1
- package/.next/standalone/.next/static/chunks/3bp284gjf7dyq.js +0 -1
- package/.next/standalone/.next/static/chunks/3k1f6clbn3gj3.js +0 -7
- package/.next/standalone/.next/static/chunks/3l4t8je6iguzc.js +0 -1
- package/.next/standalone/.next/static/chunks/3tplzuc9jk-tl.js +0 -1
- package/.next/standalone/.next/static/chunks/42m0bqr8pbovz.js +0 -11
- package/.next/static/chunks/02pq76u7y4v9-.js +0 -22
- package/.next/static/chunks/0cmq0exd4d0h9.js +0 -1
- package/.next/static/chunks/0i02yxppzds1o.js +0 -1
- package/.next/static/chunks/0m_p4k-q2qh--.js +0 -1
- package/.next/static/chunks/0oyslj62jkubd.js +0 -1
- package/.next/static/chunks/0vo-l8hxneba6.css +0 -3
- package/.next/static/chunks/0yh8z2tbrc5i_.js +0 -1
- package/.next/static/chunks/17-rhp8sg4m1b.js +0 -44
- package/.next/static/chunks/172e10mi-tpo_.js +0 -34
- package/.next/static/chunks/1ei5vbe2uw6fl.js +0 -1
- package/.next/static/chunks/1f7_pl_wq7lh3.js +0 -11
- package/.next/static/chunks/1jzbe7zdx9vbj.js +0 -2
- package/.next/static/chunks/1m3017gp790pw.js +0 -45
- package/.next/static/chunks/1t20e8u0st7ep.js +0 -1
- package/.next/static/chunks/2-ch9rm27eril.js +0 -11
- package/.next/static/chunks/2ldv-v3s9yaif.js +0 -1
- package/.next/static/chunks/3bp284gjf7dyq.js +0 -1
- package/.next/static/chunks/3k1f6clbn3gj3.js +0 -7
- package/.next/static/chunks/3l4t8je6iguzc.js +0 -1
- package/.next/static/chunks/3tplzuc9jk-tl.js +0 -1
- package/.next/static/chunks/42m0bqr8pbovz.js +0 -11
- /package/.next/standalone/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_ssgManifest.js +0 -0
- /package/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_buildManifest.js +0 -0
- /package/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{dQUvvVSXqbkEFC3-CNcwT → ZVpLBK-2vpllv2HxOFs2d}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
// Backups: copies of the whole network kept in backups/, so the app keeps
|
|
2
|
+
// working without anyone looking after it (Blake, 2026-10-03: proper backups
|
|
3
|
+
// of his connections and photos).
|
|
4
|
+
//
|
|
5
|
+
// No new format. A backup is a .sixdegrees file, the same one Settings →
|
|
6
|
+
// Your data exports (lib/data-export.js): the database (the settings in it),
|
|
7
|
+
// the photos of the people in it and the scanner's files, each file with its
|
|
8
|
+
// SHA-256. So a backup is checked by the same importer that would restore it
|
|
9
|
+
// (validateImport), and restoring one is an import of it (stageImport), with
|
|
10
|
+
// the same Restart now and the same copy of what was there kept first.
|
|
11
|
+
//
|
|
12
|
+
// Never the Social tab's files (BACKUP_SOCIAL). Its messages are kept only
|
|
13
|
+
// while Keep my messages is on, and Forget it deletes them and the numbers
|
|
14
|
+
// (lib/social-store.js): a copy made every day and kept for a week, or for
|
|
15
|
+
// good, would keep what Forget it promised to delete. An export still carries
|
|
16
|
+
// them when its box is ticked: someone chose that copy. Because a backup
|
|
17
|
+
// brings none, restoring one leaves this computer's Social files where they
|
|
18
|
+
// are (applyPendingImport steps them aside only for a copy that brings its own).
|
|
19
|
+
//
|
|
20
|
+
// Written aside as a hidden .partial, synced, checked, and only then renamed
|
|
21
|
+
// to its name (the vacuumCopy pattern, lib/data-import.js): a file with a
|
|
22
|
+
// backup's name is always a whole one that passed the importer's checks. A
|
|
23
|
+
// copy that fails its check is never kept under a backup's name, so it can
|
|
24
|
+
// never push a good one out of the rotation.
|
|
25
|
+
//
|
|
26
|
+
// When they are made: before a new version first opens the data
|
|
27
|
+
// (lib/db-client.js backupOnNewVersion), once a day, checked when the server
|
|
28
|
+
// starts and after each scan (no timers: nothing runs that nobody started),
|
|
29
|
+
// and when someone clicks Back up now. Which are kept is KEEP below.
|
|
30
|
+
|
|
31
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
32
|
+
import {
|
|
33
|
+
rmSync, renameSync, mkdirSync, lstatSync, copyFileSync, mkdtempSync,
|
|
34
|
+
} from 'node:fs';
|
|
35
|
+
import path from 'node:path';
|
|
36
|
+
import { buildExport, countPeople, EXPORT_EXTENSION } from './data-export.js';
|
|
37
|
+
import { validateImport, stageImport, ImportError } from './data-import.js';
|
|
38
|
+
import {
|
|
39
|
+
listBackups, namesInFolder, IMPORT_BACKUP_PREFIX, UPLOAD_WORK_PREFIX, sweepLeftovers,
|
|
40
|
+
} from './data-folder.js';
|
|
41
|
+
import { runningNow } from './scan-state.js';
|
|
42
|
+
import { durable } from './durable.js';
|
|
43
|
+
|
|
44
|
+
/** The kinds makeBackup makes. A copy kept before an import is made by the import itself (applyPendingImport). */
|
|
45
|
+
export const MADE_KINDS = ['daily', 'before-update', 'manual'];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* How many of each kind are kept, newest first. One made by hand (Back up
|
|
49
|
+
* now, or anything someone put in backups/ themselves) is never deleted.
|
|
50
|
+
*/
|
|
51
|
+
export const KEEP = Object.freeze({ daily: 7, 'before-update': 3, 'before-import': 3 });
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A copy kept before an import (or a restore) is the only way back from it, so
|
|
55
|
+
* it stays for at least this long however many imports follow.
|
|
56
|
+
*/
|
|
57
|
+
export const IMPORT_FLOOR_MS = 30 * 24 * 3600 * 1000;
|
|
58
|
+
|
|
59
|
+
/** Whether a backup carries the Social tab's files: never (see the top of this file). */
|
|
60
|
+
export const BACKUP_SOCIAL = false;
|
|
61
|
+
|
|
62
|
+
/** A daily backup is made when the newest is at least this old. */
|
|
63
|
+
export const DAILY_EVERY_MS = 24 * 3600 * 1000;
|
|
64
|
+
|
|
65
|
+
/** backups/ names before this feature: the database alone, before a new version (lib/db-client.js). */
|
|
66
|
+
export const AUTO_BACKUP_PREFIX = 'auto-before-';
|
|
67
|
+
|
|
68
|
+
// A half-written backup a stopped server left: hidden, so no listing shows it,
|
|
69
|
+
// and swept once it is an hour old (never one a running backup is writing).
|
|
70
|
+
const PARTIAL = /^\..+\.partial(?:-journal)?$/;
|
|
71
|
+
const PARTIAL_AGE_MS = 3600 * 1000;
|
|
72
|
+
|
|
73
|
+
const STAMP = /(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z/;
|
|
74
|
+
const MADE = /^(daily|before-update|manual)-(.+)-(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z)\.sixdegrees$/;
|
|
75
|
+
const SIDE = /^(.+\.sqlite)-(?:wal|shm|journal)$/;
|
|
76
|
+
const FILES = /^(.+)-files$/;
|
|
77
|
+
|
|
78
|
+
/** The time in a name, as every backup's name carries it (2026-10-03T14-05-09-123Z), or null. */
|
|
79
|
+
export function stampTime(name) {
|
|
80
|
+
const m = STAMP.exec(String(name));
|
|
81
|
+
if (!m) return null;
|
|
82
|
+
const t = Date.parse(`${m[1]}T${m[2]}:${m[3]}:${m[4]}.${m[5]}Z`);
|
|
83
|
+
return Number.isFinite(t) ? t : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const stampOf = (now) => now.toISOString().replace(/[:.]/g, '-');
|
|
87
|
+
|
|
88
|
+
/** "daily-0.7.0-2026-10-03T14-05-09-123Z.sixdegrees" */
|
|
89
|
+
export function backupName(kind, version, now = new Date()) {
|
|
90
|
+
return `${kind}-${version}-${stampOf(now)}${EXPORT_EXTENSION}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** What one name in backups/ is: its kind, its format, and whether it can be restored. */
|
|
94
|
+
function describe(entry, autoPrefix) {
|
|
95
|
+
const { name } = entry;
|
|
96
|
+
const out = {
|
|
97
|
+
name,
|
|
98
|
+
kind: 'manual',
|
|
99
|
+
format: null, // 'sixdegrees' (photos and files inside) or 'database' (the database alone)
|
|
100
|
+
version: null,
|
|
101
|
+
restorable: false,
|
|
102
|
+
byHand: true, // put in backups/ by someone, not made by Six Degrees
|
|
103
|
+
withFiles: null, // the folder of photos and files kept beside a database copy
|
|
104
|
+
parts: [], // names that go with it: that folder, a -wal or -shm
|
|
105
|
+
bytes: entry.bytes,
|
|
106
|
+
modifiedAt: entry.modifiedAt,
|
|
107
|
+
at: stampTime(name) ?? entry.modifiedAt,
|
|
108
|
+
folder: entry.folder,
|
|
109
|
+
};
|
|
110
|
+
if (entry.folder) return out;
|
|
111
|
+
const made = MADE.exec(name);
|
|
112
|
+
if (made) {
|
|
113
|
+
Object.assign(out, { kind: made[1], version: made[2], format: 'sixdegrees', byHand: false });
|
|
114
|
+
} else if (name.startsWith(autoPrefix) && name.endsWith('.sqlite')) {
|
|
115
|
+
const rest = name.slice(autoPrefix.length, -'.sqlite'.length);
|
|
116
|
+
const at = STAMP.exec(rest);
|
|
117
|
+
Object.assign(out, {
|
|
118
|
+
kind: 'before-update', format: 'database', byHand: false,
|
|
119
|
+
version: at ? rest.slice(0, at.index).replace(/-$/, '') || null : null,
|
|
120
|
+
});
|
|
121
|
+
} else if (name.startsWith(IMPORT_BACKUP_PREFIX) && name.endsWith('.sqlite')) {
|
|
122
|
+
Object.assign(out, { kind: 'before-import', format: 'database', byHand: false });
|
|
123
|
+
} else if (name.endsWith(EXPORT_EXTENSION)) {
|
|
124
|
+
out.format = 'sixdegrees';
|
|
125
|
+
} else if (name.endsWith('.sqlite')) {
|
|
126
|
+
out.format = 'database';
|
|
127
|
+
}
|
|
128
|
+
out.restorable = out.format !== null;
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Every backup in `folder`, newest first, as the Settings page lists them: a
|
|
134
|
+
* database copy with its -wal, -shm and its folder of photos and files is one
|
|
135
|
+
* backup. Names come from the folder's own listing (lib/data-folder.js
|
|
136
|
+
* listBackups), so nothing here is ever a path someone sent.
|
|
137
|
+
*/
|
|
138
|
+
export function describeBackups(folder, { autoPrefix = AUTO_BACKUP_PREFIX } = {}) {
|
|
139
|
+
const raw = listBackups(folder, { autoPrefix });
|
|
140
|
+
const names = new Set(raw.map((b) => b.name));
|
|
141
|
+
const owners = new Map();
|
|
142
|
+
const extras = [];
|
|
143
|
+
for (const b of raw) {
|
|
144
|
+
const side = SIDE.exec(b.name);
|
|
145
|
+
if (!b.folder && side && names.has(side[1])) { extras.push([side[1], b]); continue; }
|
|
146
|
+
const files = FILES.exec(b.name);
|
|
147
|
+
if (b.folder && files && names.has(`${files[1]}.sqlite`)) { extras.push([`${files[1]}.sqlite`, b]); continue; }
|
|
148
|
+
owners.set(b.name, describe(b, autoPrefix));
|
|
149
|
+
}
|
|
150
|
+
for (const [owner, b] of extras) {
|
|
151
|
+
const o = owners.get(owner);
|
|
152
|
+
o.bytes += b.bytes || 0;
|
|
153
|
+
o.parts.push(b.name);
|
|
154
|
+
if (b.folder) o.withFiles = b.name;
|
|
155
|
+
}
|
|
156
|
+
return [...owners.values()].sort((a, b) => b.at - a.at || b.name.localeCompare(a.name));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Delete one backup and whatever goes with it. Only names from the listing. */
|
|
160
|
+
function removeBackup(folder, entry) {
|
|
161
|
+
for (const name of [...entry.parts, entry.name]) {
|
|
162
|
+
rmSync(path.join(folder, name), { recursive: true, force: true });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Keep KEEP of each kind, newest first; never one made by hand; never a copy
|
|
168
|
+
* from before an import that is under 30 days old. Also sweeps half-written
|
|
169
|
+
* backups a stopped server left. @returns the names deleted.
|
|
170
|
+
*/
|
|
171
|
+
export function rotateBackups(folder, { now = Date.now(), keep = KEEP, floorMs = IMPORT_FLOOR_MS, autoPrefix = AUTO_BACKUP_PREFIX } = {}) {
|
|
172
|
+
const at = now instanceof Date ? now.getTime() : now;
|
|
173
|
+
const all = describeBackups(folder, { autoPrefix });
|
|
174
|
+
const removed = [];
|
|
175
|
+
for (const [kind, n] of Object.entries(keep)) {
|
|
176
|
+
for (const entry of all.filter((e) => e.kind === kind && !e.byHand).slice(n)) {
|
|
177
|
+
if (kind === 'before-import' && at - entry.at < floorMs) continue;
|
|
178
|
+
removeBackup(folder, entry);
|
|
179
|
+
removed.push(entry.name);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const name of namesInFolder(folder)) {
|
|
183
|
+
if (!PARTIAL.test(name)) continue;
|
|
184
|
+
try {
|
|
185
|
+
if (at - lstatSync(path.join(folder, name)).mtimeMs >= PARTIAL_AGE_MS) rmSync(path.join(folder, name), { force: true });
|
|
186
|
+
} catch { /* gone already */ }
|
|
187
|
+
}
|
|
188
|
+
if (removed.length) durable.syncFolder(folder);
|
|
189
|
+
return removed;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── checking ─────────────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
// Checked once per file per server: a check reads every page and every photo,
|
|
195
|
+
// and the page asks often. The same file (name, size and time) gives the same
|
|
196
|
+
// answer; a file that changes on the disk is checked again. One map per
|
|
197
|
+
// process however the bundler splits route modules, like lib/scan-state.js.
|
|
198
|
+
const CHECKED = Symbol.for('six-degrees.backups-checked');
|
|
199
|
+
const FAILED = Symbol.for('six-degrees.backup-failed');
|
|
200
|
+
const checkedFiles = () => (globalThis[CHECKED] ||= new Map());
|
|
201
|
+
|
|
202
|
+
/** The importer's refusal, without the words that only fit an import. */
|
|
203
|
+
export function plainProblem(message) {
|
|
204
|
+
return String(message || 'it could not be read')
|
|
205
|
+
.replace(/,? so nothing was imported\..*$/s, '.')
|
|
206
|
+
.replace(/\s+Export it again on the other computer\.?$/, '');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Would this backup restore? The importer's own checks (validateImport): SQLite's
|
|
211
|
+
* integrity check, nothing an export never holds, every row count and every
|
|
212
|
+
* file's checksum against its manifest.
|
|
213
|
+
* @returns {{ ok: true, people, photos, exportedAt, fromVersion } | { ok: false, problem }}
|
|
214
|
+
*/
|
|
215
|
+
export function checkBackup(file, { applySchema, schemaSql, appVersion }) {
|
|
216
|
+
try {
|
|
217
|
+
const info = validateImport(file, { applySchema, schemaSql, appVersion });
|
|
218
|
+
return { ok: true, ...info };
|
|
219
|
+
} catch (err) {
|
|
220
|
+
const problem = plainProblem(err instanceof ImportError ? err.message : `It could not be read (${err.message}).`);
|
|
221
|
+
return { ok: false, problem: problem.replace(/^This file/, 'This backup') };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function fileKey(file) {
|
|
226
|
+
try {
|
|
227
|
+
const s = lstatSync(file);
|
|
228
|
+
return s.isFile() ? `${file}|${s.size}|${s.mtimeMs}` : null;
|
|
229
|
+
} catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** checkBackup, remembered for the same file. */
|
|
235
|
+
export function checkBackupOnce(file, opts) {
|
|
236
|
+
const key = fileKey(file);
|
|
237
|
+
if (!key) return { ok: false, problem: 'It is no longer there.' };
|
|
238
|
+
const memo = checkedFiles();
|
|
239
|
+
if (!memo.has(key)) memo.set(key, checkBackup(file, opts));
|
|
240
|
+
return memo.get(key);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** The last backup that couldn't be made in this server, for the page; cleared by the next that works. */
|
|
244
|
+
export function backupFailure() {
|
|
245
|
+
return globalThis[FAILED] || null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function noteBackupFailure(kind, err, now = new Date()) {
|
|
249
|
+
globalThis[FAILED] = { kind, at: now.toISOString(), message: plainProblem(err?.message || String(err)) };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── making ───────────────────────────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Make a backup of the open database `db` and the data folder `dataDir` in
|
|
256
|
+
* `backupsDir`: the whole network as a .sixdegrees file, with every photo of
|
|
257
|
+
* someone in it and the scanner's files (not the Social tab's: BACKUP_SOCIAL).
|
|
258
|
+
*
|
|
259
|
+
* `appVersion` is the version making it (in its name); `dataVersion` the one
|
|
260
|
+
* whose data it holds (in its manifest), which differs only before an update:
|
|
261
|
+
* that copy holds the old version's data, so the old version can restore it.
|
|
262
|
+
*
|
|
263
|
+
* @returns {{ name, file, bytes, people, photos, kind, verified: true }}
|
|
264
|
+
* @throws when it can't be made or fails its check; nothing is left under a backup's name then.
|
|
265
|
+
*/
|
|
266
|
+
export function makeBackup(kind, {
|
|
267
|
+
db, dataDir, backupsDir, appVersion, dataVersion = appVersion, schemaSql, applySchema, now = new Date(), rotate = true,
|
|
268
|
+
}) {
|
|
269
|
+
if (!MADE_KINDS.includes(kind)) throw new Error(`There is no kind of backup called '${kind}'.`);
|
|
270
|
+
mkdirSync(backupsDir, { recursive: true });
|
|
271
|
+
const name = backupName(kind, appVersion, now);
|
|
272
|
+
const file = path.join(backupsDir, name);
|
|
273
|
+
const partial = path.join(backupsDir, `.${name}.partial`);
|
|
274
|
+
const tidy = () => {
|
|
275
|
+
rmSync(partial, { force: true });
|
|
276
|
+
rmSync(`${partial}-journal`, { force: true });
|
|
277
|
+
};
|
|
278
|
+
tidy();
|
|
279
|
+
let made;
|
|
280
|
+
try {
|
|
281
|
+
made = buildExport(db, {
|
|
282
|
+
dir: dataDir, outFile: partial, includePhotos: true, includeSocial: BACKUP_SOCIAL, appVersion: dataVersion, schemaSql, now,
|
|
283
|
+
});
|
|
284
|
+
// SQLite doesn't sync what VACUUM INTO writes (lib/durable.js).
|
|
285
|
+
durable.syncFile(partial);
|
|
286
|
+
const check = checkBackup(partial, { applySchema, schemaSql, appVersion });
|
|
287
|
+
if (!check.ok) throw new Error(`the copy failed its check: ${check.problem}`);
|
|
288
|
+
renameSync(partial, file);
|
|
289
|
+
durable.syncFolder(backupsDir);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
tidy();
|
|
292
|
+
throw err;
|
|
293
|
+
}
|
|
294
|
+
const key = fileKey(file);
|
|
295
|
+
if (key) checkedFiles().set(key, { ok: true, people: made.people, photos: made.photos });
|
|
296
|
+
globalThis[FAILED] = null;
|
|
297
|
+
if (rotate) {
|
|
298
|
+
// A backup that was made stays made, even if an old one can't be deleted.
|
|
299
|
+
try { rotateBackups(backupsDir, { now }); } catch { /* tried again after the next one */ }
|
|
300
|
+
}
|
|
301
|
+
return { name, file, kind, bytes: made.bytes, people: made.people, photos: made.photos, verified: true };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** When was the newest daily backup made? null when there is none. */
|
|
305
|
+
export function newestDaily(backupsDir) {
|
|
306
|
+
return describeBackups(backupsDir).find((e) => e.kind === 'daily')?.at ?? null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Is a daily backup due? When there is none, or the newest is a day old. One
|
|
311
|
+
* stamped more than a day ahead of now (the clock was changed) doesn't hold
|
|
312
|
+
* the next one back forever.
|
|
313
|
+
*/
|
|
314
|
+
export function dailyDue(newest, now = Date.now()) {
|
|
315
|
+
if (newest == null) return true;
|
|
316
|
+
return now - newest >= DAILY_EVERY_MS || newest - now >= DAILY_EVERY_MS;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* The daily backup, if one is due and there is a network to back up. Called
|
|
321
|
+
* when the server opens the database and after each scan. @returns the backup, or null.
|
|
322
|
+
*/
|
|
323
|
+
export function dailyBackupIfDue(opts) {
|
|
324
|
+
const now = opts.now || new Date();
|
|
325
|
+
if (!dailyDue(newestDaily(opts.backupsDir), now.getTime())) return null;
|
|
326
|
+
if (countPeople(opts.db) === 0) return null;
|
|
327
|
+
try {
|
|
328
|
+
return makeBackup('daily', { ...opts, now });
|
|
329
|
+
} catch (err) {
|
|
330
|
+
noteBackupFailure('daily', err, now);
|
|
331
|
+
throw err;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ── what the page shows ──────────────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* The backups for the Settings page, newest first, and the newest restorable
|
|
339
|
+
* .sixdegrees backup checked (once per file per server): "Last backup: <time>,
|
|
340
|
+
* verified", or what is wrong with it (TRAPS §7: a damaged copy says so).
|
|
341
|
+
*/
|
|
342
|
+
export function backupReport(backupsDir, { applySchema, schemaSql, appVersion }) {
|
|
343
|
+
const backups = describeBackups(backupsDir);
|
|
344
|
+
const newest = backups.find((e) => e.format === 'sixdegrees' && !e.byHand) || null;
|
|
345
|
+
let last = null;
|
|
346
|
+
if (newest) {
|
|
347
|
+
const check = checkBackupOnce(path.join(backupsDir, newest.name), { applySchema, schemaSql, appVersion });
|
|
348
|
+
last = {
|
|
349
|
+
name: newest.name,
|
|
350
|
+
kind: newest.kind,
|
|
351
|
+
at: new Date(newest.at).toISOString(),
|
|
352
|
+
verified: check.ok,
|
|
353
|
+
people: check.ok ? check.people ?? null : null,
|
|
354
|
+
problem: check.ok ? null : check.problem,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
return { backups, last, failed: backupFailure(), keep: KEEP, floorDays: IMPORT_FLOOR_MS / (24 * 3600 * 1000) };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ── restoring ────────────────────────────────────────────────────────────────
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* The checks before a restore, as an import makes them (lib/data-import.js
|
|
364
|
+
* importPreflight): nothing running, no import waiting, and a yes that names
|
|
365
|
+
* how many people it replaces. @returns an ImportError, or null to go ahead.
|
|
366
|
+
*/
|
|
367
|
+
export function restorePreflight({ scanRunning, pending, currentPeople = 0, confirmedPeople }) {
|
|
368
|
+
if (scanRunning) {
|
|
369
|
+
return new ImportError(`${runningNow(scanRunning)}. Let it finish, or stop it on the Scan page, then restore.`, { status: 409 });
|
|
370
|
+
}
|
|
371
|
+
if (pending) {
|
|
372
|
+
return new ImportError('An import or a restore is already waiting to finish. Restart Six Degrees to finish it, or cancel it first.', { status: 409 });
|
|
373
|
+
}
|
|
374
|
+
if (currentPeople > 0 && confirmedPeople !== currentPeople) {
|
|
375
|
+
return new ImportError(
|
|
376
|
+
`This copy has ${currentPeople.toLocaleString('en-US')} people. Confirm that the backup replaces them.`,
|
|
377
|
+
{ status: 409, needsConfirm: true, people: currentPeople },
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* The backup called `name`, only if it is one of the names backups/ holds now
|
|
385
|
+
* (lib/data-folder.js listBackups) and, unless `restorable` is false (Show in
|
|
386
|
+
* Finder), can be restored. Never a path: a name with a folder in it, "..", or
|
|
387
|
+
* anything the folder doesn't list is refused.
|
|
388
|
+
*/
|
|
389
|
+
export function findBackup(backupsDir, name, { restorable = true } = {}) {
|
|
390
|
+
if (typeof name !== 'string' || !name || name !== path.basename(name) || name === '.' || name === '..'
|
|
391
|
+
|| /[\\/\0]/.test(name)) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
if (!listBackups(backupsDir).some((b) => b.name === name)) return null;
|
|
395
|
+
return describeBackups(backupsDir).find((e) => e.name === name && (e.restorable || !restorable)) || null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Stage a backup to replace the network at the next start: the same as an
|
|
400
|
+
* import (stageImport), from a copy of the backup in a private working folder,
|
|
401
|
+
* so the backup itself is never moved or changed. A database-only copy (from
|
|
402
|
+
* before this feature, or kept before an import) is made into a .sixdegrees
|
|
403
|
+
* file first, with the photos and files kept beside it, or, for one that has
|
|
404
|
+
* none, the photos and files here now.
|
|
405
|
+
* @returns what the page shows while it waits (READY).
|
|
406
|
+
* @throws ImportError, fit to show.
|
|
407
|
+
*/
|
|
408
|
+
export function restoreBackup(name, {
|
|
409
|
+
dir, backupsDir, applySchema, schemaSql, appVersion, replacedPeople = 0, now = new Date(),
|
|
410
|
+
}) {
|
|
411
|
+
const entry = findBackup(backupsDir, name);
|
|
412
|
+
if (!entry) throw new ImportError('There is no backup by that name in the backups folder.', { status: 404 });
|
|
413
|
+
sweepLeftovers(dir);
|
|
414
|
+
const work = mkdtempSync(path.join(dir, UPLOAD_WORK_PREFIX));
|
|
415
|
+
try {
|
|
416
|
+
const upload = path.join(work, 'upload.sixdegrees');
|
|
417
|
+
const source = path.join(backupsDir, entry.name);
|
|
418
|
+
if (entry.format === 'sixdegrees') {
|
|
419
|
+
copyFileSync(source, upload);
|
|
420
|
+
} else {
|
|
421
|
+
// Read from a private copy: SQLite may write beside a database it opens,
|
|
422
|
+
// and nothing is ever written into backups/ by a restore.
|
|
423
|
+
const copy = path.join(work, 'backup.sqlite');
|
|
424
|
+
copyFileSync(source, copy);
|
|
425
|
+
let db;
|
|
426
|
+
try {
|
|
427
|
+
db = new DatabaseSync(copy);
|
|
428
|
+
} catch (err) {
|
|
429
|
+
throw new ImportError(`This backup couldn't be opened (${err.message}).`);
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
// A copy kept before an import has the files that import stepped
|
|
433
|
+
// aside beside it, the Social tab's among them only if the import
|
|
434
|
+
// brought its own: undoing it puts them back. One with none beside it
|
|
435
|
+
// takes the photos and scanner files here now, and leaves the Social
|
|
436
|
+
// tab's where they are.
|
|
437
|
+
buildExport(db, {
|
|
438
|
+
dir: entry.withFiles ? path.join(backupsDir, entry.withFiles) : dir,
|
|
439
|
+
outFile: upload, includePhotos: true, includeSocial: Boolean(entry.withFiles), appVersion, schemaSql, now,
|
|
440
|
+
});
|
|
441
|
+
} catch (err) {
|
|
442
|
+
throw new ImportError(`This backup couldn't be read (${err.message}).`);
|
|
443
|
+
} finally {
|
|
444
|
+
db.close();
|
|
445
|
+
}
|
|
446
|
+
rmSync(copy, { force: true });
|
|
447
|
+
}
|
|
448
|
+
return stageImport(upload, {
|
|
449
|
+
dir, applySchema, schemaSql, appVersion, replacedPeople, now, restoredFrom: entry.name,
|
|
450
|
+
});
|
|
451
|
+
} catch (err) {
|
|
452
|
+
rmSync(work, { recursive: true, force: true });
|
|
453
|
+
if (err instanceof ImportError) {
|
|
454
|
+
throw new ImportError(plainProblem(err.message).replace(/^This file/, 'This backup'), { status: err.status, ...err.extra });
|
|
455
|
+
}
|
|
456
|
+
throw err;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
@@ -296,3 +296,35 @@ function spread(wanted, sep) {
|
|
|
296
296
|
seq.forEach((i, k) => { out[i] = placed[k]; });
|
|
297
297
|
return out;
|
|
298
298
|
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* How much is going on in each circle, for the order Bridge Chains rings them in:
|
|
302
|
+
* the busiest on the inner ring (Blake, 2026-10-03: "the inner most ring to have
|
|
303
|
+
* the clusters with the most amount of notifications ... a cluster that has been
|
|
304
|
+
* formed after scanning or if someone added back and is available to scan").
|
|
305
|
+
* Every notification about the circle counts, an unread one twice; so does each
|
|
306
|
+
* person in it who added you back and is ready to scan, and each cluster of
|
|
307
|
+
* their own that has formed from it since.
|
|
308
|
+
*
|
|
309
|
+
* @param {Array<{id: any}>} circles
|
|
310
|
+
* @param {{ notes?: Array<{circle: string|null, seen?: boolean}>, ready?: Map, chained?: Map }} facts
|
|
311
|
+
* @returns {Map<any, {score: number, notes: number, unread: number, ready: number, chains: number}>}
|
|
312
|
+
*/
|
|
313
|
+
export function circleActivity(circles, { notes = [], ready = new Map(), chained = new Map() } = {}) {
|
|
314
|
+
const byCircle = new Map();
|
|
315
|
+
for (const n of notes) {
|
|
316
|
+
if (n?.circle == null) continue;
|
|
317
|
+
const c = byCircle.get(n.circle) || { notes: 0, unread: 0 };
|
|
318
|
+
c.notes += 1;
|
|
319
|
+
if (!n.seen) c.unread += 1;
|
|
320
|
+
byCircle.set(n.circle, c);
|
|
321
|
+
}
|
|
322
|
+
const out = new Map();
|
|
323
|
+
for (const b of circles) {
|
|
324
|
+
const n = byCircle.get(String(b.id)) || { notes: 0, unread: 0 };
|
|
325
|
+
const r = ready.get(b.id) || 0;
|
|
326
|
+
const ch = chained.get(b.id) || 0;
|
|
327
|
+
out.set(b.id, { score: n.notes + n.unread + r + ch, notes: n.notes, unread: n.unread, ready: r, chains: ch });
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// A LinkedIn Connections.csv import, kept in the data folder (csv-network.json)
|
|
2
|
+
// so it is still there after the window closes and after the app restarts.
|
|
3
|
+
//
|
|
4
|
+
// Kept beside the database, never merged into it. The 1.0 list (2026-10-03)
|
|
5
|
+
// chose this over adding the rows to linkedin_connections as 1st degree:
|
|
6
|
+
// merging means matching CSV people to rows the scanner wrote by profile link,
|
|
7
|
+
// under a profile the CSV knows nothing about, and that is how people were
|
|
8
|
+
// duplicated, hidden and deleted before (TRAPS §19, §25, §36). Here the map
|
|
9
|
+
// reads the file instead of the database while it's open, as it always did
|
|
10
|
+
// from the window, and deleting the file is the whole of removing it.
|
|
11
|
+
//
|
|
12
|
+
// Only what the export says about each person is kept, packed as lib/csv.js
|
|
13
|
+
// packConnections makes it: id, name, position, company, profile link and the
|
|
14
|
+
// date you connected. Scores and tiers are worked out again on every load, so
|
|
15
|
+
// they follow the scoring model. The Email Address column is never read, so it
|
|
16
|
+
// can't be here.
|
|
17
|
+
//
|
|
18
|
+
// The file is read as untrusted: an import from another computer brings one
|
|
19
|
+
// (lib/data-folder.js NETWORK_FILES), and a hand edit or a bad disk can change
|
|
20
|
+
// it. A file that doesn't hold what this writes is never shown as a network;
|
|
21
|
+
// the page says it couldn't be read instead (TRAPS §7).
|
|
22
|
+
|
|
23
|
+
import { readFileSync, lstatSync, rmSync } from 'node:fs';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import { durable } from './durable.js';
|
|
26
|
+
import { CSV_NETWORK_FILE } from './data-folder.js';
|
|
27
|
+
|
|
28
|
+
export { CSV_NETWORK_FILE };
|
|
29
|
+
|
|
30
|
+
/** Bump when the file's layout changes in a way this version couldn't read. */
|
|
31
|
+
export const CSV_FORMAT = 1;
|
|
32
|
+
|
|
33
|
+
/** Each packed row, in order (lib/csv.js packConnections). */
|
|
34
|
+
export const CSV_COLUMNS = ['id', 'name', 'role', 'company', 'profile_url', 'connected_date'];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* LinkedIn allows 30,000 connections. This leaves room, and still bounds what
|
|
38
|
+
* one request can make the app keep.
|
|
39
|
+
*/
|
|
40
|
+
export const MAX_CSV_PEOPLE = 50000;
|
|
41
|
+
const MAX_FIELD = 2000;
|
|
42
|
+
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
43
|
+
|
|
44
|
+
/** A refusal whose message is fit to show inside a sentence. Nothing was written. */
|
|
45
|
+
export class CsvStoreError extends Error {}
|
|
46
|
+
|
|
47
|
+
const text = (v, { empty = true } = {}) => typeof v === 'string' && v.length <= MAX_FIELD && (empty || v.trim() !== '');
|
|
48
|
+
|
|
49
|
+
/** Why `rows` aren't a packed import this app would make, or null when they are. */
|
|
50
|
+
export function csvRowsProblem(rows) {
|
|
51
|
+
if (!Array.isArray(rows)) return 'it has no list of people';
|
|
52
|
+
if (!rows.length) return 'it has nobody in it';
|
|
53
|
+
if (rows.length > MAX_CSV_PEOPLE) return `it has ${rows.length.toLocaleString('en-US')} people, more than the ${MAX_CSV_PEOPLE.toLocaleString('en-US')} it can hold`;
|
|
54
|
+
const ids = new Set();
|
|
55
|
+
for (let i = 0; i < rows.length; i++) {
|
|
56
|
+
const r = rows[i];
|
|
57
|
+
const which = `person ${i + 1}`;
|
|
58
|
+
if (!Array.isArray(r) || r.length !== CSV_COLUMNS.length) return `${which} isn't laid out the way the import keeps people`;
|
|
59
|
+
const [id, name, role, company, profileUrl, connected] = r;
|
|
60
|
+
if (!text(id, { empty: false }) || !text(name, { empty: false })) return `${which} has a name or id that is missing or too long`;
|
|
61
|
+
if (!text(role) || !text(company)) return `${which} has a position or company that isn't text, or is too long`;
|
|
62
|
+
if (profileUrl !== null && !text(profileUrl)) return `${which} has a profile link that isn't text, or is too long`;
|
|
63
|
+
if (connected !== null && !(typeof connected === 'string' && ISO_DATE.test(connected))) return `${which} has a date that isn't one`;
|
|
64
|
+
if (ids.has(id)) return `two people share the id ${JSON.stringify(id.slice(0, 40))}`;
|
|
65
|
+
ids.add(id);
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Why `doc` isn't a kept import this app wrote, or null when it is. */
|
|
71
|
+
export function csvNetworkProblem(doc) {
|
|
72
|
+
if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return "it isn't what Six Degrees writes";
|
|
73
|
+
if (doc.format !== CSV_FORMAT) {
|
|
74
|
+
return Number.isInteger(doc.format) && doc.format > CSV_FORMAT
|
|
75
|
+
? 'it was written by a newer Six Degrees than this one'
|
|
76
|
+
: "it isn't what Six Degrees writes";
|
|
77
|
+
}
|
|
78
|
+
if (typeof doc.importedAt !== 'string' || Number.isNaN(Date.parse(doc.importedAt))) return "it doesn't say when it was imported";
|
|
79
|
+
return csvRowsProblem(doc.connections);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The kept import in `dir`: `{ csv: { connections, importedAt, people }, problem: null }`,
|
|
84
|
+
* `{ csv: null, problem: null }` when there is none, or `{ csv: null, problem }`
|
|
85
|
+
* when a file is there but can't be used. Never throws.
|
|
86
|
+
*/
|
|
87
|
+
export function readCsvNetwork(dir) {
|
|
88
|
+
const file = path.join(dir, CSV_NETWORK_FILE);
|
|
89
|
+
let st;
|
|
90
|
+
try {
|
|
91
|
+
st = lstatSync(file);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (err.code === 'ENOENT') return { csv: null, problem: null };
|
|
94
|
+
return { csv: null, problem: `it couldn't be opened (${err.code || err.message})` };
|
|
95
|
+
}
|
|
96
|
+
// A link is never followed, as nowhere else in the data folder is.
|
|
97
|
+
if (!st.isFile()) return { csv: null, problem: "it isn't a plain file" };
|
|
98
|
+
let doc;
|
|
99
|
+
try {
|
|
100
|
+
doc = JSON.parse(readFileSync(file, 'utf8'));
|
|
101
|
+
} catch (err) {
|
|
102
|
+
return { csv: null, problem: err instanceof SyntaxError ? "it isn't readable JSON" : `it couldn't be read (${err.code || err.message})` };
|
|
103
|
+
}
|
|
104
|
+
const problem = csvNetworkProblem(doc);
|
|
105
|
+
if (problem) return { csv: null, problem };
|
|
106
|
+
return { csv: { connections: doc.connections, importedAt: doc.importedAt, people: doc.connections.length }, problem: null };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Keep `connections` (packed rows) as the import, in place of any kept before.
|
|
111
|
+
* Written whole or not at all, and on the disk before this returns
|
|
112
|
+
* (lib/durable.js): a temporary file, synced, then renamed over the old one.
|
|
113
|
+
* @throws {CsvStoreError} when the rows aren't an import this app would make.
|
|
114
|
+
*/
|
|
115
|
+
export function writeCsvNetwork(dir, connections, { now = new Date() } = {}) {
|
|
116
|
+
const problem = csvRowsProblem(connections);
|
|
117
|
+
if (problem) throw new CsvStoreError(problem);
|
|
118
|
+
const doc = { format: CSV_FORMAT, source: 'csv', importedAt: now.toISOString(), columns: CSV_COLUMNS, connections };
|
|
119
|
+
durable.writeFileDurably(path.join(dir, CSV_NETWORK_FILE), JSON.stringify(doc));
|
|
120
|
+
return { people: connections.length, importedAt: doc.importedAt };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Remove the kept import. Returns whether there was one. */
|
|
124
|
+
export function removeCsvNetwork(dir) {
|
|
125
|
+
const file = path.join(dir, CSV_NETWORK_FILE);
|
|
126
|
+
let had = true;
|
|
127
|
+
try { lstatSync(file); } catch { had = false; }
|
|
128
|
+
rmSync(file, { force: true });
|
|
129
|
+
if (had) durable.syncFolder(dir);
|
|
130
|
+
return had;
|
|
131
|
+
}
|