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
|
|
};
|
|
|
|
|