You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							329 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							329 lines
						
					
					
						
							11 KiB
						
					
					
				
								#!/usr/bin/env node
							 | 
						|
								/**
							 | 
						|
								 * Seed Test Projects for Localhost Testing
							 | 
						|
								 * 
							 | 
						|
								 * Creates test project data for localhost API server testing.
							 | 
						|
								 * Can be run standalone or integrated into your local dev server.
							 | 
						|
								 * 
							 | 
						|
								 * @author Matthew Raymer
							 | 
						|
								 * @version 1.0.0
							 | 
						|
								 */
							 | 
						|
								
							 | 
						|
								const http = require('http');
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Generate a valid ULID (26 characters, Crockford base32)
							 | 
						|
								 * Simplified version for testing - not cryptographically secure
							 | 
						|
								 */
							 | 
						|
								function generateULID() {
							 | 
						|
								  const timestamp = Date.now();
							 | 
						|
								  // Crockford base32 alphabet (0-9, A-Z excluding I, L, O, U)
							 | 
						|
								  const alphabet = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
							 | 
						|
								  const timestampPart = timestamp.toString(36).toUpperCase().padStart(10, '0');
							 | 
						|
								  const randomPart = Array.from({ length: 16 }, () => 
							 | 
						|
								    alphabet[Math.floor(Math.random() * alphabet.length)]
							 | 
						|
								  ).join('');
							 | 
						|
								  return (timestampPart + randomPart).substring(0, 26);
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Generate a valid plan handle ID in URI format
							 | 
						|
								 * Default format: https://endorser.ch/entity/{ULID}
							 | 
						|
								 */
							 | 
						|
								function generateValidPlanHandleId() {
							 | 
						|
								  return `https://endorser.ch/entity/${generateULID()}`;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Generate a test JWT ID with timestamp prefix for sorting
							 | 
						|
								 */
							 | 
						|
								function generateJwtId(timestamp = Date.now()) {
							 | 
						|
								  const random = Math.random().toString(36).substring(2, 8);
							 | 
						|
								  const hash = Math.random().toString(36).substring(2, 10);
							 | 
						|
								  return `${Math.floor(timestamp / 1000)}_${random}_${hash}`;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Generate test project data
							 | 
						|
								 */
							 | 
						|
								function generateTestProject(handleId, index = 0) {
							 | 
						|
								  const now = Date.now();
							 | 
						|
								  const startTime = new Date(now + (index * 86400000)); // 1 day apart
							 | 
						|
								  const endTime = new Date(startTime.getTime() + (30 * 86400000)); // 30 days later
							 | 
						|
								
							 | 
						|
								  return {
							 | 
						|
								    jwtId: generateJwtId(now + (index * 1000)),
							 | 
						|
								    handleId: handleId,
							 | 
						|
								    name: `Test Project ${index + 1}`,
							 | 
						|
								    description: `This is test project ${index + 1} for localhost prefetch testing. Created for User Zero starred plans querying.`,
							 | 
						|
								    issuerDid: "did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F",
							 | 
						|
								    agentDid: "did:test:agent_" + index,
							 | 
						|
								    startTime: startTime.toISOString(),
							 | 
						|
								    endTime: endTime.toISOString(),
							 | 
						|
								    locLat: 40.7128 + (index * 0.1), // Vary location slightly
							 | 
						|
								    locLon: -74.0060 + (index * 0.1),
							 | 
						|
								    url: `https://test-project-${index + 1}.timesafari.test`,
							 | 
						|
								    version: "1.0.0"
							 | 
						|
								  };
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Generate complete test project with previous claim
							 | 
						|
								 */
							 | 
						|
								function generateTestProjectWithClaim(handleId, index = 0) {
							 | 
						|
								  const project = generateTestProject(handleId, index);
							 | 
						|
								  const previousClaimJwtId = generateJwtId(Date.now() - (86400000 * 2)); // 2 days ago
							 | 
						|
								
							 | 
						|
								  return {
							 | 
						|
								    planSummary: project,
							 | 
						|
								    previousClaim: {
							 | 
						|
								      jwtId: previousClaimJwtId,
							 | 
						|
								      claimType: "project_update",
							 | 
						|
								      claimData: {
							 | 
						|
								        message: `Previous update for ${handleId}`,
							 | 
						|
								        version: "0.9.0"
							 | 
						|
								      },
							 | 
						|
								      metadata: {
							 | 
						|
								        createdAt: new Date(Date.now() - (86400000 * 2)).toISOString(),
							 | 
						|
								        updatedAt: new Date(Date.now() - (86400000)).toISOString()
							 | 
						|
								      }
							 | 
						|
								    }
							 | 
						|
								  };
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Default test project IDs from config
							 | 
						|
								 * 
							 | 
						|
								 * Plan handle IDs must be valid URIs (RFC 3986 format):
							 | 
						|
								 * - Default format: https://endorser.ch/entity/{26-char-ULID}
							 | 
						|
								 * - ULID is 26 characters, Crockford base32 encoded
							 | 
						|
								 * - Any valid URI scheme is accepted: http://, https://, did:, etc.
							 | 
						|
								 * 
							 | 
						|
								 * For real testing, use actual plan handle IDs from your TimeSafari database.
							 | 
						|
								 * To get valid IDs:
							 | 
						|
								 * 1. Create projects in TimeSafari app and note handleId from API responses
							 | 
						|
								 * 2. Query your local database: SELECT handleId FROM plans LIMIT 5
							 | 
						|
								 * 3. Check starred projects: settings.starredPlanHandleIds
							 | 
						|
								 * 
							 | 
						|
								 * The IDs below are auto-generated valid URIs for testing structure only.
							 | 
						|
								 * Replace with real IDs from your database for actual prefetch testing.
							 | 
						|
								 */
							 | 
						|
								const DEFAULT_TEST_PROJECT_IDS = [
							 | 
						|
								  // Auto-generate valid format IDs for testing
							 | 
						|
								  generateValidPlanHandleId(),
							 | 
						|
								  generateValidPlanHandleId(),
							 | 
						|
								  generateValidPlanHandleId(),
							 | 
						|
								  generateValidPlanHandleId(),
							 | 
						|
								  generateValidPlanHandleId()
							 | 
						|
								];
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Generate all test projects
							 | 
						|
								 */
							 | 
						|
								function generateAllTestProjects(projectIds = DEFAULT_TEST_PROJECT_IDS) {
							 | 
						|
								  return projectIds.map((handleId, index) =>
							 | 
						|
								    generateTestProjectWithClaim(handleId, index)
							 | 
						|
								  );
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Seed projects to localhost API server
							 | 
						|
								 * 
							 | 
						|
								 * **IMPORTANT**: The TimeSafari API creates plans via POST `/api/v2/claim` with PlanAction JWTs.
							 | 
						|
								 * This function attempts to POST to `/api/test/seed-projects` if your API has a custom test seed endpoint.
							 | 
						|
								 * 
							 | 
						|
								 * For real TimeSafari APIs:
							 | 
						|
								 * 1. Create plans via the TimeSafari app UI (recommended)
							 | 
						|
								 * 2. Import PlanAction JWTs via POST `/api/v2/claim` (requires DID signing)
							 | 
						|
								 * 3. Use direct database inserts (not recommended)
							 | 
						|
								 * 
							 | 
						|
								 * This seed function is primarily for custom test endpoints or the test-api-server-with-seed.js
							 | 
						|
								 */
							 | 
						|
								function seedToLocalhost(apiUrl, projectIds = DEFAULT_TEST_PROJECT_IDS) {
							 | 
						|
								  return new Promise((resolve, reject) => {
							 | 
						|
								    const projects = generateAllTestProjects(projectIds);
							 | 
						|
								    
							 | 
						|
								    console.log(`📦 Generating ${projects.length} test projects...`);
							 | 
						|
								    projects.forEach((project, index) => {
							 | 
						|
								      console.log(`  ${index + 1}. ${project.planSummary.handleId} - ${project.planSummary.name}`);
							 | 
						|
								    });
							 | 
						|
								
							 | 
						|
								    console.log('');
							 | 
						|
								    console.log('⚠️  NOTE: This attempts to POST to /api/test/seed-projects');
							 | 
						|
								    console.log('   If your API doesn\'t have this endpoint, create plans via:');
							 | 
						|
								    console.log('   1. TimeSafari App UI (easiest)');
							 | 
						|
								    console.log('   2. POST /api/v2/claim with PlanAction JWT (requires DID signing)');
							 | 
						|
								    console.log('   3. Direct database inserts');
							 | 
						|
								    console.log('');
							 | 
						|
								
							 | 
						|
								    // If your API has a seed endpoint, use this:
							 | 
						|
								    const seedUrl = `${apiUrl}/api/test/seed-projects`;
							 | 
						|
								    const postData = JSON.stringify({ projects });
							 | 
						|
								
							 | 
						|
								    const options = {
							 | 
						|
								      method: 'POST',
							 | 
						|
								      headers: {
							 | 
						|
								        'Content-Type': 'application/json',
							 | 
						|
								        'Content-Length': Buffer.byteLength(postData)
							 | 
						|
								      }
							 | 
						|
								    };
							 | 
						|
								
							 | 
						|
								    const req = http.request(seedUrl, options, (res) => {
							 | 
						|
								      let data = '';
							 | 
						|
								
							 | 
						|
								      res.on('data', (chunk) => {
							 | 
						|
								        data += chunk;
							 | 
						|
								      });
							 | 
						|
								
							 | 
						|
								      res.on('end', () => {
							 | 
						|
								        if (res.statusCode === 200 || res.statusCode === 201) {
							 | 
						|
								          console.log('✅ Test projects seeded successfully');
							 | 
						|
								          console.log(`📊 Response: ${data}`);
							 | 
						|
								          resolve(JSON.parse(data));
							 | 
						|
								        } else {
							 | 
						|
								          console.error(`❌ Seed failed: ${res.statusCode} ${res.statusMessage}`);
							 | 
						|
								          console.error(`Response: ${data}`);
							 | 
						|
								          reject(new Error(`HTTP ${res.statusCode}: ${data}`));
							 | 
						|
								        }
							 | 
						|
								      });
							 | 
						|
								    });
							 | 
						|
								
							 | 
						|
								    req.on('error', (error) => {
							 | 
						|
								      console.error(`❌ Request error: ${error.message}`);
							 | 
						|
								      reject(error);
							 | 
						|
								    });
							 | 
						|
								
							 | 
						|
								    req.write(postData);
							 | 
						|
								    req.end();
							 | 
						|
								  });
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Generate mock API server response data
							 | 
						|
								 * 
							 | 
						|
								 * Returns the data structure that your localhost API should return
							 | 
						|
								 */
							 | 
						|
								function getMockApiResponse(projectIds = DEFAULT_TEST_PROJECT_IDS, afterId = null) {
							 | 
						|
								  const allProjects = generateAllTestProjects(projectIds);
							 | 
						|
								  
							 | 
						|
								  // Filter by afterId if provided (simple comparison by jwtId)
							 | 
						|
								  let filteredProjects = allProjects;
							 | 
						|
								  if (afterId) {
							 | 
						|
								    const afterIndex = allProjects.findIndex(p => p.planSummary.jwtId === afterId);
							 | 
						|
								    if (afterIndex >= 0) {
							 | 
						|
								      filteredProjects = allProjects.slice(afterIndex + 1);
							 | 
						|
								    } else {
							 | 
						|
								      // If afterId not found, return all (for testing)
							 | 
						|
								      filteredProjects = allProjects;
							 | 
						|
								    }
							 | 
						|
								  }
							 | 
						|
								
							 | 
						|
								  const nextAfterId = filteredProjects.length > 0
							 | 
						|
								    ? filteredProjects[filteredProjects.length - 1].planSummary.jwtId
							 | 
						|
								    : null;
							 | 
						|
								
							 | 
						|
								  return {
							 | 
						|
								    data: filteredProjects,
							 | 
						|
								    hitLimit: false,
							 | 
						|
								    pagination: {
							 | 
						|
								      hasMore: false,
							 | 
						|
								      nextAfterId: nextAfterId
							 | 
						|
								    }
							 | 
						|
								  };
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Export project data as JSON file
							 | 
						|
								 */
							 | 
						|
								function exportToJSON(filename = 'test-projects.json', projectIds = DEFAULT_TEST_PROJECT_IDS) {
							 | 
						|
								  const fs = require('fs');
							 | 
						|
								  const projects = generateAllTestProjects(projectIds);
							 | 
						|
								  const data = {
							 | 
						|
								    projects: projects,
							 | 
						|
								    generatedAt: new Date().toISOString(),
							 | 
						|
								    count: projects.length
							 | 
						|
								  };
							 | 
						|
								
							 | 
						|
								  fs.writeFileSync(filename, JSON.stringify(data, null, 2));
							 | 
						|
								  console.log(`💾 Exported ${projects.length} test projects to ${filename}`);
							 | 
						|
								  return data;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// CLI usage
							 | 
						|
								if (require.main === module) {
							 | 
						|
								  const args = process.argv.slice(2);
							 | 
						|
								  const command = args[0];
							 | 
						|
								
							 | 
						|
								  switch (command) {
							 | 
						|
								    case 'export':
							 | 
						|
								      {
							 | 
						|
								        const filename = args[1] || 'test-projects.json';
							 | 
						|
								        const projectIds = args[2] ? args[2].split(',') : DEFAULT_TEST_PROJECT_IDS;
							 | 
						|
								        
							 | 
						|
								        // Warn if using auto-generated IDs (they'll be valid URIs but not from real database)
							 | 
						|
								        console.log('📝 Using provided plan handle IDs.');
							 | 
						|
								        console.log('   Note: For real testing, use IDs from your TimeSafari database.');
							 | 
						|
								        console.log('   Valid format: https://endorser.ch/entity/{ULID} or any valid URI');
							 | 
						|
								        console.log('');
							 | 
						|
								        
							 | 
						|
								        exportToJSON(filename, projectIds);
							 | 
						|
								      }
							 | 
						|
								      break;
							 | 
						|
								
							 | 
						|
								    case 'seed':
							 | 
						|
								      {
							 | 
						|
								        const apiUrl = args[1] || 'http://localhost:3000';
							 | 
						|
								        const projectIds = args[2] ? args[2].split(',') : DEFAULT_TEST_PROJECT_IDS;
							 | 
						|
								        
							 | 
						|
								        // Validate plan IDs are valid URIs (RFC 3986)
							 | 
						|
								        const invalidIds = projectIds.filter(id => !id.match(/^[A-Za-z][A-Za-z0-9+.-]+:/));
							 | 
						|
								        if (invalidIds.length > 0) {
							 | 
						|
								          console.error('❌ ERROR: Invalid plan handle ID format.');
							 | 
						|
								          console.error('   Plan handle IDs must be valid URIs (RFC 3986).');
							 | 
						|
								          console.error('   Format: https://endorser.ch/entity/{ULID}');
							 | 
						|
								          console.error('   Example: https://endorser.ch/entity/01GQBE7Q0RQQAGJMEEW6RSGKTF');
							 | 
						|
								          console.error(`   Invalid IDs: ${invalidIds.join(', ')}`);
							 | 
						|
								          console.error('');
							 | 
						|
								          console.error('   Usage: node scripts/seed-test-projects.js seed <apiUrl> "<uri1>,<uri2>,<uri3>"');
							 | 
						|
								          process.exit(1);
							 | 
						|
								        }
							 | 
						|
								        
							 | 
						|
								        seedToLocalhost(apiUrl, projectIds)
							 | 
						|
								          .then(() => {
							 | 
						|
								            console.log('✅ Seeding complete');
							 | 
						|
								            process.exit(0);
							 | 
						|
								          })
							 | 
						|
								          .catch((error) => {
							 | 
						|
								            console.error('❌ Seeding failed:', error.message);
							 | 
						|
								            process.exit(1);
							 | 
						|
								          });
							 | 
						|
								      }
							 | 
						|
								      break;
							 | 
						|
								
							 | 
						|
								    case 'generate':
							 | 
						|
								    default:
							 | 
						|
								      {
							 | 
						|
								        const projectIds = args[1] ? args[1].split(',') : DEFAULT_TEST_PROJECT_IDS;
							 | 
						|
								        
							 | 
						|
								        console.log('📝 Generated test projects with valid URI format handle IDs.');
							 | 
						|
								        console.log('   Format: https://endorser.ch/entity/{ULID}');
							 | 
						|
								        console.log('   For real testing, replace with IDs from your TimeSafari database.');
							 | 
						|
								        
							 | 
						|
								        const projects = generateAllTestProjects(projectIds);
							 | 
						|
								        console.log(JSON.stringify({ data: projects }, null, 2));
							 | 
						|
								      }
							 | 
						|
								      break;
							 | 
						|
								  }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								module.exports = {
							 | 
						|
								  generateTestProject,
							 | 
						|
								  generateTestProjectWithClaim,
							 | 
						|
								  generateAllTestProjects,
							 | 
						|
								  getMockApiResponse,
							 | 
						|
								  seedToLocalhost,
							 | 
						|
								  exportToJSON,
							 | 
						|
								  DEFAULT_TEST_PROJECT_IDS,
							 | 
						|
								  generateJwtId
							 | 
						|
								};
							 | 
						|
								
							 | 
						|
								
							 |