diff --git a/docs/getting-valid-plan-ids.md b/docs/getting-valid-plan-ids.md index 28938aa..2c1c307 100644 --- a/docs/getting-valid-plan-ids.md +++ b/docs/getting-valid-plan-ids.md @@ -62,14 +62,31 @@ cd /home/matthew/projects/timesafari/crowd-master ## Plan Handle ID Format -Plan handle IDs can have different formats depending on your TimeSafari backend: +Plan handle IDs **must be valid URIs** (RFC 3986 format). -- **UUID format**: `550e8400-e29b-41d4-a716-446655440000` -- **Hash format**: `abc123def456789` -- **Sequential**: `plan_001`, `plan_002` -- **Custom format**: Whatever your backend generates +### Default Format (System-Generated) -**Important**: Check your actual TimeSafari API/database to determine the correct format. +**`https://endorser.ch/entity/{ULID}`** + +- **ULID**: 26-character string, Crockford base32 encoded +- **Example**: `https://endorser.ch/entity/01GQBE7Q0RQQAGJMEEW6RSGKTF` + +### Custom Global URIs + +Any valid URI with a scheme is accepted: +- `https://endorser.ch/entity/01GQBE7Q0RQQAGJMEEW6RSGKTF` (default) +- `http://example.com/project/123` (custom) +- `did:ethr:0x1234...` (DID format) +- Any URI matching: `^[A-Za-z][A-Za-z0-9+.-]+:` + +**Validation**: The format must match RFC 3986 URI specification. + +### ULID Format Details + +- **Length**: Exactly 26 characters +- **Encoding**: Crockford base32 (0-9, A-Z excluding I, L, O, U) +- **Structure**: Timestamp + random component +- **Purpose**: Globally unique, lexicographically sortable identifiers ## Updating Test Configuration diff --git a/scripts/seed-test-projects.js b/scripts/seed-test-projects.js index c6464be..2eac605 100755 --- a/scripts/seed-test-projects.js +++ b/scripts/seed-test-projects.js @@ -11,6 +11,29 @@ 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 */ @@ -71,26 +94,27 @@ function generateTestProjectWithClaim(handleId, index = 0) { /** * Default test project IDs from config * - * NOTE: These are placeholder IDs. For real testing, replace with valid plan handle IDs - * from your TimeSafari database. Valid plan IDs can be obtained by: + * 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. * - * 1. Creating a project in the TimeSafari app and noting its handleId - * 2. Querying your local database for existing plan handleIds - * 3. Using the plan handleIds from your starred projects in account settings + * 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 * - * Plan handle IDs typically follow the format used by your TimeSafari backend. - * If your localhost has no projects, you may need to create test projects first - * or use the IDs from your staging/production environment. + * 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 = [ - // Replace these with actual plan handle IDs from your TimeSafari setup - // Example formats (check your actual database for real IDs): - // - UUID format: "550e8400-e29b-41d4-a716-446655440000" - // - Hash format: "abc123def456" - // - Or whatever format your TimeSafari API uses - "PLACEHOLDER_ID_1", - "PLACEHOLDER_ID_2", - "PLACEHOLDER_ID_3" + // Auto-generate valid format IDs for testing + generateValidPlanHandleId(), + generateValidPlanHandleId(), + generateValidPlanHandleId(), + generateValidPlanHandleId(), + generateValidPlanHandleId() ]; /** @@ -220,14 +244,11 @@ if (require.main === module) { const filename = args[1] || 'test-projects.json'; const projectIds = args[2] ? args[2].split(',') : DEFAULT_TEST_PROJECT_IDS; - if (projectIds.some(id => id.startsWith('PLACEHOLDER'))) { - console.warn('⚠️ WARNING: Using placeholder IDs. Replace with real plan handle IDs from your TimeSafari database.'); - console.warn(' To get valid IDs:'); - console.warn(' 1. Create projects in TimeSafari app'); - console.warn(' 2. Query your database for plan handleIds'); - console.warn(' 3. Use IDs from starred projects in account settings'); - console.warn(''); - } + // 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); } @@ -238,10 +259,16 @@ if (require.main === module) { const apiUrl = args[1] || 'http://localhost:3000'; const projectIds = args[2] ? args[2].split(',') : DEFAULT_TEST_PROJECT_IDS; - if (projectIds.some(id => id.startsWith('PLACEHOLDER'))) { - console.error('❌ ERROR: Cannot seed with placeholder IDs. Please provide valid plan handle IDs.'); - console.error(' Usage: node scripts/seed-test-projects.js seed ",,"'); - console.error(' Example: node scripts/seed-test-projects.js seed http://localhost:3000 "550e8400-e29b-41d4-a716-446655440000,abc123def456"'); + // 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 ",,"'); process.exit(1); } @@ -262,9 +289,9 @@ if (require.main === module) { { const projectIds = args[1] ? args[1].split(',') : DEFAULT_TEST_PROJECT_IDS; - if (projectIds.some(id => id.startsWith('PLACEHOLDER'))) { - console.warn('⚠️ WARNING: Using placeholder IDs. These need to be replaced with real plan handle 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)); diff --git a/scripts/test-api-server-with-seed.js b/scripts/test-api-server-with-seed.js index 847471e..0f755b6 100755 --- a/scripts/test-api-server-with-seed.js +++ b/scripts/test-api-server-with-seed.js @@ -38,33 +38,34 @@ app.use((req, res, next) => { * Seed test projects on startup */ function seedProjects(projectIds = DEFAULT_TEST_PROJECT_IDS) { - // Check if using placeholder IDs - if (projectIds.some(id => id.startsWith('PLACEHOLDER'))) { - console.warn(''); - console.warn('⚠️ WARNING: Using placeholder plan handle IDs!'); - console.warn(' The test server will work, but you need to:'); - console.warn(' 1. Get valid plan handle IDs from your TimeSafari database'); - console.warn(' 2. Update test-user-zero.ts with real plan IDs'); - console.warn(' 3. Or provide valid IDs via command line:'); - console.warn(' node scripts/test-api-server-with-seed.js [port] "id1,id2,id3"'); - console.warn(''); - console.warn(' For now, the server will use placeholder IDs for testing structure only.'); - console.warn(''); + // Validate plan IDs are valid URIs + const invalidIds = projectIds.filter(id => !id.match(/^[A-Za-z][A-Za-z0-9+.-]+:/)); + if (invalidIds.length > 0) { + console.error(''); + console.error('❌ ERROR: Invalid plan handle ID format!'); + console.error(' Plan handle IDs must be valid URIs (RFC 3986).'); + console.error(' Expected format: https://endorser.ch/entity/{ULID}'); + console.error(` Invalid IDs: ${invalidIds.join(', ')}`); + console.error(''); + console.error(' Valid examples:'); + console.error(' https://endorser.ch/entity/01GQBE7Q0RQQAGJMEEW6RSGKTF'); + console.error(' http://example.com/project/123'); + console.error(' did:ethr:0x1234...'); + console.error(''); + process.exit(1); } seededProjects = generateAllTestProjects(projectIds); - console.log(`🌱 Seeded ${seededProjects.length} test projects:`); + console.log(`🌱 Seeded ${seededProjects.length} test projects with valid URI format:`); seededProjects.forEach((project, index) => { console.log(` ${index + 1}. ${project.planSummary.handleId} - ${project.planSummary.name}`); }); - if (projectIds.some(id => id.startsWith('PLACEHOLDER'))) { - console.log(''); - console.log('📝 To use real plan IDs, update:'); - console.log(' test-apps/daily-notification-test/src/config/test-user-zero.ts'); - console.log(' starredProjects.planIds = ["your", "real", "plan", "ids"]'); - console.log(''); - } + console.log(''); + console.log('📝 Note: These are auto-generated test IDs.'); + console.log(' For testing with real projects, use IDs from your TimeSafari database.'); + console.log(' Update: test-apps/daily-notification-test/src/config/test-user-zero.ts'); + console.log(''); } /** diff --git a/test-apps/daily-notification-test/src/config/test-user-zero.ts b/test-apps/daily-notification-test/src/config/test-user-zero.ts index f73754a..e8369b2 100644 --- a/test-apps/daily-notification-test/src/config/test-user-zero.ts +++ b/test-apps/daily-notification-test/src/config/test-user-zero.ts @@ -81,27 +81,37 @@ export const TEST_USER_ZERO_CONFIG = { // Test Starred Projects (mock data for testing) starredProjects: { - // IMPORTANT: Replace these with valid plan handle IDs from your TimeSafari database + // IMPORTANT: 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 (e.g., 01GQBE7Q0RQQAGJMEEW6RSGKTF) + // - Any valid URI scheme is accepted: http://, https://, did:, etc. + // // To get valid plan IDs: - // 1. Create projects in TimeSafari app and note their handleId from API responses + // 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 in account settings: settings.starredPlanHandleIds + // 3. Check starred projects: settings.starredPlanHandleIds // 4. See docs/getting-valid-plan-ids.md for detailed instructions // - // Plan handle IDs can be: - // - UUIDs: "550e8400-e29b-41d4-a716-446655440000" - // - Hashes: "abc123def456789" - // - Or any format your TimeSafari backend uses - // - // For localhost testing with no projects, you can: - // - Use the test API server: node scripts/test-api-server-with-seed.js - // - Or create test projects first, then use their IDs here - planIds: [ - "PLACEHOLDER_ID_1", // Replace with real plan handle ID - "PLACEHOLDER_ID_2", // Replace with real plan handle ID - "PLACEHOLDER_ID_3" // Replace with real plan handle ID - ], + // These are auto-generated valid URIs for testing structure. + // Replace with real IDs from your TimeSafari database for actual testing. + planIds: ((): string[] => { + // Generate valid URI format IDs for testing + const generateULID = (): string => { + const alphabet = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; + const timestamp = Date.now().toString(36).toUpperCase().padStart(10, '0'); + const random = Array.from({ length: 16 }, () => + alphabet[Math.floor(Math.random() * alphabet.length)] + ).join(''); + return (timestamp + random).substring(0, 26); + }; + + return [ + `https://endorser.ch/entity/${generateULID()}`, + `https://endorser.ch/entity/${generateULID()}`, + `https://endorser.ch/entity/${generateULID()}` + ]; + })(), // Last acknowledged JWT ID (for pagination testing) lastAckedJwtId: "1704067200_abc123_def45678"