@@ -7,7 +7,8 @@ import type {
77 ProvisionRequest ,
88 ProvisioningService ,
99} from "../../services/ProvisioningService" ;
10- import type { DbService } from "../db/db.service" ;
10+ import { DbService } from "../db/db.service" ;
11+ import { connectWithRetry } from "../db/retry-neo4j" ;
1112import { type TypedReply , type TypedRequest , WatcherRequest } from "./types" ;
1213
1314interface WatcherSignatureRequest {
@@ -341,7 +342,7 @@ export async function registerHttpRoutes(
341342 const { payload } = await jose . jwtVerify ( token , JWKS ) ;
342343
343344 console . log (
344- ` Token validation: Token verified successfully, payload:` ,
345+ " Token validation: Token verified successfully, payload:" ,
345346 payload ,
346347 ) ;
347348 return payload ;
@@ -356,7 +357,7 @@ export async function registerHttpRoutes(
356357 ) ;
357358 }
358359 if ( error . cause ) {
359- console . error ( ` Token validation error cause:` , error . cause ) ;
360+ console . error ( " Token validation error cause:" , error . cause ) ;
360361 }
361362 return null ;
362363 }
@@ -421,8 +422,7 @@ export async function registerHttpRoutes(
421422 }
422423
423424 const authHeader =
424- request . headers . authorization ||
425- request . headers [ "Authorization" ] ;
425+ request . headers . authorization || request . headers . Authorization ;
426426 const tokenPayload = await validateToken (
427427 typeof authHeader === "string" ? authHeader : null ,
428428 ) ;
@@ -516,4 +516,192 @@ export async function registerHttpRoutes(
516516 } ,
517517 ) ;
518518 }
519+
520+ // Emover endpoint - Copy metaEnvelopes to new evault instance
521+ server . post < {
522+ Body : {
523+ eName : string ;
524+ targetNeo4jUri : string ;
525+ targetNeo4jUser : string ;
526+ targetNeo4jPassword : string ;
527+ } ;
528+ } > (
529+ "/emover" ,
530+ {
531+ schema : {
532+ tags : [ "migration" ] ,
533+ description :
534+ "Copy all metaEnvelopes for an eName to a new evault instance" ,
535+ body : {
536+ type : "object" ,
537+ required : [
538+ "eName" ,
539+ "targetNeo4jUri" ,
540+ "targetNeo4jUser" ,
541+ "targetNeo4jPassword" ,
542+ ] ,
543+ properties : {
544+ eName : { type : "string" } ,
545+ targetNeo4jUri : { type : "string" } ,
546+ targetNeo4jUser : { type : "string" } ,
547+ targetNeo4jPassword : { type : "string" } ,
548+ } ,
549+ } ,
550+ response : {
551+ 200 : {
552+ type : "object" ,
553+ properties : {
554+ success : { type : "boolean" } ,
555+ count : { type : "number" } ,
556+ message : { type : "string" } ,
557+ } ,
558+ } ,
559+ 400 : {
560+ type : "object" ,
561+ properties : {
562+ error : { type : "string" } ,
563+ } ,
564+ } ,
565+ 500 : {
566+ type : "object" ,
567+ properties : {
568+ error : { type : "string" } ,
569+ } ,
570+ } ,
571+ } ,
572+ } ,
573+ } ,
574+ async (
575+ request : TypedRequest < {
576+ eName : string ;
577+ targetNeo4jUri : string ;
578+ targetNeo4jUser : string ;
579+ targetNeo4jPassword : string ;
580+ } > ,
581+ reply : TypedReply ,
582+ ) => {
583+ const {
584+ eName,
585+ targetNeo4jUri,
586+ targetNeo4jUser,
587+ targetNeo4jPassword,
588+ } = request . body ;
589+
590+ if ( ! dbService ) {
591+ return reply . status ( 500 ) . send ( {
592+ error : "Database service not available" ,
593+ } ) ;
594+ }
595+
596+ try {
597+ console . log (
598+ `[MIGRATION] Starting migration for eName: ${ eName } to target evault` ,
599+ ) ;
600+
601+ // Step 1: Validate eName exists in current evault
602+ const existingMetaEnvelopes =
603+ await dbService . findAllMetaEnvelopesByEName ( eName ) ;
604+ if ( existingMetaEnvelopes . length === 0 ) {
605+ console . log (
606+ `[MIGRATION] No metaEnvelopes found for eName: ${ eName } ` ,
607+ ) ;
608+ return reply . status ( 400 ) . send ( {
609+ error : `No metaEnvelopes found for eName: ${ eName } ` ,
610+ } ) ;
611+ }
612+
613+ console . log (
614+ `[MIGRATION] Found ${ existingMetaEnvelopes . length } metaEnvelopes for eName: ${ eName } ` ,
615+ ) ;
616+
617+ // Step 2: Create connection to target evault's Neo4j
618+ console . log (
619+ `[MIGRATION] Connecting to target Neo4j at: ${ targetNeo4jUri } ` ,
620+ ) ;
621+ const targetDriver = await connectWithRetry (
622+ targetNeo4jUri ,
623+ targetNeo4jUser ,
624+ targetNeo4jPassword ,
625+ ) ;
626+ const targetDbService = new DbService ( targetDriver ) ;
627+
628+ try {
629+ // Step 3: Copy all metaEnvelopes to target evault
630+ console . log (
631+ `[MIGRATION] Copying ${ existingMetaEnvelopes . length } metaEnvelopes to target evault` ,
632+ ) ;
633+ const copiedCount =
634+ await dbService . copyMetaEnvelopesToNewEvaultInstance (
635+ eName ,
636+ targetDbService ,
637+ ) ;
638+
639+ // Step 4: Verify copy
640+ console . log (
641+ `[MIGRATION] Verifying copy: checking ${ copiedCount } metaEnvelopes in target evault` ,
642+ ) ;
643+ const targetMetaEnvelopes =
644+ await targetDbService . findAllMetaEnvelopesByEName (
645+ eName ,
646+ ) ;
647+
648+ if (
649+ targetMetaEnvelopes . length !==
650+ existingMetaEnvelopes . length
651+ ) {
652+ const error = `Copy verification failed: expected ${ existingMetaEnvelopes . length } metaEnvelopes, found ${ targetMetaEnvelopes . length } ` ;
653+ console . error ( `[MIGRATION ERROR] ${ error } ` ) ;
654+ return reply . status ( 500 ) . send ( { error } ) ;
655+ }
656+
657+ // Verify IDs match
658+ const sourceIds = new Set (
659+ existingMetaEnvelopes . map ( ( m ) => m . id ) ,
660+ ) ;
661+ const targetIds = new Set (
662+ targetMetaEnvelopes . map ( ( m ) => m . id ) ,
663+ ) ;
664+
665+ if ( sourceIds . size !== targetIds . size ) {
666+ const error =
667+ "Copy verification failed: ID count mismatch" ;
668+ console . error ( `[MIGRATION ERROR] ${ error } ` ) ;
669+ return reply . status ( 500 ) . send ( { error } ) ;
670+ }
671+
672+ for ( const id of sourceIds ) {
673+ if ( ! targetIds . has ( id ) ) {
674+ const error = `Copy verification failed: missing metaEnvelope ID: ${ id } ` ;
675+ console . error ( `[MIGRATION ERROR] ${ error } ` ) ;
676+ return reply . status ( 500 ) . send ( { error } ) ;
677+ }
678+ }
679+
680+ console . log (
681+ `[MIGRATION] Verification successful: ${ copiedCount } metaEnvelopes copied and verified` ,
682+ ) ;
683+
684+ // Close target connection
685+ await targetDriver . close ( ) ;
686+
687+ return {
688+ success : true ,
689+ count : copiedCount ,
690+ message : `Successfully copied ${ copiedCount } metaEnvelopes to target evault` ,
691+ } ;
692+ } catch ( copyError ) {
693+ await targetDriver . close ( ) ;
694+ throw copyError ;
695+ }
696+ } catch ( error ) {
697+ console . error ( `[MIGRATION ERROR] Migration failed:` , error ) ;
698+ return reply . status ( 500 ) . send ( {
699+ error :
700+ error instanceof Error
701+ ? error . message
702+ : "Failed to migrate metaEnvelopes" ,
703+ } ) ;
704+ }
705+ } ,
706+ ) ;
519707}
0 commit comments