PSA/server/seeds/dev/88_alice_wonderland_project_template.cjs
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

437 lines
16 KiB
JavaScript

/**
* Seed an Alice in Wonderland themed project template for dev environment
* "Down the Rabbit Hole: A Curiouser and Curiouser Migration"
*
* NOTE: estimated_hours is stored in MINUTES (not hours) in the database
*/
const crypto = require('crypto');
// Use Node.js built-in crypto.randomUUID() instead of uuid package
const uuidv4 = () => crypto.randomUUID();
const TEMPLATE_NAME = 'Down the Rabbit Hole Migration';
const TEMPLATE_CATEGORY = 'Wonderland';
const TEMPLATE_DESCRIPTION = 'A most curious project template for navigating the peculiar landscape of system migrations. As the Cheshire Cat would say, "We\'re all mad here" - but this template will keep you from losing your head!';
// Standard status definitions with colors
const STANDARD_STATUSES = [
{ name: 'To Do', color: '#6B7280', is_closed: false, order_number: 1 },
{ name: 'In Progress', color: '#3B82F6', is_closed: false, order_number: 2 },
{ name: 'Blocked', color: '#EF4444', is_closed: false, order_number: 3 },
{ name: 'Done', color: '#10B981', is_closed: true, order_number: 4 }
];
/**
* Convert hours to minutes for database storage
*/
const hoursToMinutes = (hours) => Math.round(hours * 60);
/**
* Build the template data structure
* NOTE: estimated_hours values are in MINUTES
*/
function buildTemplateData(tenant, templateId, statusMappingIds) {
const phase1Id = uuidv4(); // Down the Rabbit Hole
const phase2Id = uuidv4(); // The Pool of Tears
const phase3Id = uuidv4(); // A Mad Tea-Party
const phase4Id = uuidv4(); // The Queen's Croquet-Ground
const toDoStatusMappingId = statusMappingIds[0];
const phases = [
{
tenant,
template_phase_id: phase1Id,
template_id: templateId,
phase_name: 'Down the Rabbit Hole',
description: 'The curious beginning where we tumble into the unknown depths of legacy systems, falling past shelves of old documentation and jars labeled "ORANGE MARMALADE" (but actually containing deprecated configs).',
duration_days: 5,
start_offset_days: 0,
order_key: 'a0'
},
{
tenant,
template_phase_id: phase2Id,
template_id: templateId,
phase_name: 'The Pool of Tears',
description: 'Where we swim through the accumulated technical debt of years past, occasionally bumping into a Mouse who knows the driest thing - proper database normalization.',
duration_days: 7,
start_offset_days: 5,
order_key: 'a1'
},
{
tenant,
template_phase_id: phase3Id,
template_id: templateId,
phase_name: 'A Mad Tea-Party',
description: 'The chaotic middle phase where nothing makes sense, time seems broken (especially timestamps), and the Hatter keeps asking "Why is a raven like a writing desk?" (Answer: Neither should store production credentials).',
duration_days: 10,
start_offset_days: 12,
order_key: 'a2'
},
{
tenant,
template_phase_id: phase4Id,
template_id: templateId,
phase_name: 'The Queen\'s Croquet-Ground',
description: 'Final validation where we play croquet with flamingo-shaped test scripts and hedgehog data packets, hoping the Queen doesn\'t shout "Off with their heads!" at our error logs.',
duration_days: 5,
start_offset_days: 22,
order_key: 'a3'
}
];
const tasks = [
// Phase 1: Down the Rabbit Hole
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase1Id,
task_name: 'Follow the White Rabbit (Initial Discovery)',
description: 'Chase that mysterious white rabbit of a legacy system through the meadow of undocumented code. Don\'t be late!',
estimated_hours: hoursToMinutes(3),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a0'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase1Id,
task_name: 'Drink the "DRINK ME" Bottle (Shrink Scope)',
description: 'Carefully consume the requirements document to shrink the project scope to a manageable size. Warning: May cause sudden feelings of smallness.',
estimated_hours: hoursToMinutes(2),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a1'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase1Id,
task_name: 'Eat the "EAT ME" Cake (Expand Infrastructure)',
description: 'Consume the infrastructure planning documents to grow large enough to reach the key of cloud scalability on that impossibly high table.',
estimated_hours: hoursToMinutes(4),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a2'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase1Id,
task_name: 'Find the Golden Key (API Credentials)',
description: 'Locate and secure all API keys, tokens, and secrets. The tiny door to the beautiful garden awaits!',
estimated_hours: hoursToMinutes(2),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a3'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase1Id,
task_name: 'Document the Fall (Technical Assessment)',
description: 'Record everything observed during the fall - the cupboards, bookshelves, maps, and pictures. Future Alice will thank you.',
estimated_hours: hoursToMinutes(3),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a4'
},
// Phase 2: The Pool of Tears
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase2Id,
task_name: 'Cry a Pool of Data (Export Legacy Data)',
description: 'Shed enough tears (data exports) to fill an entire pool. Mind the Mouse - he hates getting wet with unvalidated records.',
estimated_hours: hoursToMinutes(4),
duration_days: 2,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a0'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase2Id,
task_name: 'Swim with the Dodo (Stakeholder Alignment)',
description: 'Join the Caucus-race with stakeholders where everybody runs in circles until everyone has won and all must have prizes (sign-offs).',
estimated_hours: hoursToMinutes(3),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a1'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase2Id,
task_name: 'Learn from the Mouse (Historical Data Tales)',
description: 'Listen to the Mouse\'s long and sad tale about William the Conqueror... er, I mean, the history of your data schema migrations.',
estimated_hours: hoursToMinutes(2),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a2'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase2Id,
task_name: 'Dry Off with the Caucus Race (Data Validation)',
description: 'Run around validating data until you\'re dry (or until all records pass validation - whichever comes first).',
estimated_hours: hoursToMinutes(5),
duration_days: 2,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a3'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase2Id,
task_name: 'Find the Thimble Prize (Quick Wins)',
description: 'Identify and celebrate small victories. Alice\'s own thimble, presented with great ceremony!',
estimated_hours: hoursToMinutes(1),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a4'
},
// Phase 3: A Mad Tea-Party
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase3Id,
task_name: 'Set the Table (Environment Setup)',
description: 'Arrange the teacups (containers), teapots (services), and ensure the Dormouse (monitoring) is properly positioned in the teapot.',
estimated_hours: hoursToMinutes(4),
duration_days: 2,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a0'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase3Id,
task_name: 'Riddles with the Hatter (Problem Solving)',
description: 'Answer impossible riddles like "Why is a microservice like a writing desk?" and "Have you guessed the null pointer yet?"',
estimated_hours: hoursToMinutes(6),
duration_days: 2,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a1'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase3Id,
task_name: 'Move Down! Move Down! (Data Migration)',
description: 'Keep moving seats around the table (shuffling data between systems) because there\'s always clean cups further down!',
estimated_hours: hoursToMinutes(8),
duration_days: 3,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a2'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase3Id,
task_name: 'Wake the Dormouse (Activate Monitoring)',
description: 'Poke the Dormouse repeatedly to ensure all monitoring and alerting systems are actually awake. "Twinkle, twinkle, little bat..."',
estimated_hours: hoursToMinutes(3),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a3'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase3Id,
task_name: 'Fix the Watch (Timestamp Synchronization)',
description: 'The Hatter\'s watch is two days wrong! Butter in the works, perhaps. Ensure all system clocks and timestamps are properly synchronized.',
estimated_hours: hoursToMinutes(2),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a4'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase3Id,
task_name: 'No Room! No Room! (Capacity Planning)',
description: 'Despite the March Hare\'s protests, find room at the table for all the data. There\'s PLENTY of room if you plan properly!',
estimated_hours: hoursToMinutes(3),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a5'
},
// Phase 4: The Queen's Croquet-Ground
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase4Id,
task_name: 'Paint the Roses Red (Fix Critical Bugs)',
description: 'Quick! Paint over all the white roses (bugs) before the Queen sees them! "We planted white bugs by mistake..."',
estimated_hours: hoursToMinutes(4),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a0'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase4Id,
task_name: 'Play Croquet (User Acceptance Testing)',
description: 'Play the most confusing game of croquet ever with live flamingo test scripts and hedgehog test data that keep wandering off.',
estimated_hours: hoursToMinutes(4),
duration_days: 2,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a1'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase4Id,
task_name: 'Consult the Cheshire Cat (Get Expert Advice)',
description: '"Would you tell me which way I ought to go from here?" Get guidance on the path to production. Remember: We\'re all mad here.',
estimated_hours: hoursToMinutes(2),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a2'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase4Id,
task_name: 'Survive the Queen\'s Verdict (Go-Live Approval)',
description: 'Present your work to the Queen. Pray she doesn\'t shout "Off with their heads!" Accept that sentence first, verdict afterwards.',
estimated_hours: hoursToMinutes(2),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a3'
},
{
tenant,
template_task_id: uuidv4(),
template_phase_id: phase4Id,
task_name: 'Wake Up (Go Live & Celebrate)',
description: '"You\'re nothing but a pack of cards!" Wake up on the bank with your sister, realizing the curious dream is now production reality.',
estimated_hours: hoursToMinutes(1),
duration_days: 1,
template_status_mapping_id: toDoStatusMappingId,
order_key: 'a4'
}
];
return { phases, tasks };
}
/**
* Find existing statuses or create if missing, then create status mappings for template
* Uses: To Do, In Progress, Blocked, Done
*/
async function getOrCreateStandardStatusMappings(knex, tenant, templateId) {
const statusMappings = [];
const statusMappingIds = [];
for (const standardStatus of STANDARD_STATUSES) {
// Look up existing status by name (case-insensitive)
let status = await knex('statuses')
.where({ tenant, status_type: 'project_task' })
.whereRaw('LOWER(name) = LOWER(?)', [standardStatus.name])
.first();
// Create only if not found (fallback for missing statuses like "Blocked")
if (!status) {
const maxOrder = await knex('statuses')
.where({ tenant, status_type: 'project_task' })
.max('order_number as max')
.first();
const newStatusId = uuidv4();
await knex('statuses').insert({
tenant,
status_id: newStatusId,
name: standardStatus.name,
status_type: 'project_task',
is_closed: standardStatus.is_closed,
order_number: (maxOrder?.max || 0) + 1,
color: standardStatus.color,
created_by: null
});
status = { status_id: newStatusId, color: standardStatus.color };
console.log(` Created missing status "${standardStatus.name}" for tenant`);
}
// Create status mapping for template
const mappingId = uuidv4();
statusMappings.push({
tenant,
template_status_mapping_id: mappingId,
template_id: templateId,
status_id: status.status_id,
custom_status_name: null,
custom_status_color: standardStatus.color,
display_order: standardStatus.order_number
});
statusMappingIds.push(mappingId);
}
return { mappings: statusMappings, mappingIds: statusMappingIds };
}
exports.seed = async function (knex) {
const tenant = await knex('tenants').select('tenant').first();
if (!tenant) {
console.log('No tenant found, skipping Alice in Wonderland project template seed');
return;
}
const tenantId = tenant.tenant;
// Check if template already exists
const existing = await knex('project_templates')
.where({
tenant: tenantId,
template_name: TEMPLATE_NAME
})
.first();
if (existing) {
console.log('Alice in Wonderland project template already exists, skipping');
return;
}
// Get a user for created_by (optional, can be null for system templates)
const user = await knex('users')
.where('tenant', tenantId)
.first();
const templateId = uuidv4();
// Get or create standard status mappings (To Do, In Progress, Done)
const { mappings: statusMappings, mappingIds: statusMappingIds } =
await getOrCreateStandardStatusMappings(knex, tenantId, templateId);
// Insert in correct order: template first, then status mappings, then phases, then tasks
await knex('project_templates').insert({
tenant: tenantId,
template_id: templateId,
template_name: TEMPLATE_NAME,
description: TEMPLATE_DESCRIPTION,
category: TEMPLATE_CATEGORY,
created_by: user?.user_id || null,
use_count: 0
});
await knex('project_template_status_mappings').insert(statusMappings);
// Build and insert phases and tasks
const data = buildTemplateData(tenantId, templateId, statusMappingIds);
await knex('project_template_phases').insert(data.phases);
await knex('project_template_tasks').insert(data.tasks);
console.log('Created Alice in Wonderland project template: "Down the Rabbit Hole Migration"');
console.log(' "Curiouser and curiouser!" - Alice');
};