feat: implement complete plugin store repository synchronization system

• Fix repository sync implementation in admin API (was TODO placeholder)
- Add full fetch/parse logic for repository.json and plugin index -
Implement robot matching by name/manufacturer patterns - Handle plugin
creation/updates with proper error handling - Add comprehensive
TypeScript typing throughout

• Fix plugin store installation state detection - Add getStudyPlugins
API integration to check installed plugins - Update PluginCard component
with isInstalled prop and correct button states - Fix repository name
display using metadata.repositoryId mapping - Show "Installed"
(disabled) vs "Install" (enabled) based on actual state

• Resolve admin access and authentication issues - Add missing
administrator role to user system roles table - Fix admin route access
for repository management - Enable repository sync functionality in
admin dashboard

• Add repository metadata integration - Update plugin records with
proper repositoryId references - Add metadata field to
robots.plugins.list API response - Enable repository name display for
all plugins from metadata

• Fix TypeScript compliance across plugin system - Replace unsafe 'any'
types with proper interfaces - Add type definitions for repository and
plugin data structures - Use nullish coalescing operators for safer null
handling - Remove unnecessary type assertions

• Integrate live repository at https://repo.hristudio.com - Successfully
loads 3 robot plugins (TurtleBot3 Burger/Waffle, NAO) - Complete ROS2
action definitions with parameter schemas - Trust level categorization
(official, verified, community) - Platform and documentation metadata
preservation

• Update documentation and development workflow - Document plugin
repository system in work_in_progress.md - Update quick-reference.md
with repository sync examples - Add plugin installation and management
guidance - Remove problematic test script with TypeScript errors

BREAKING CHANGE: Plugin store now requires repository sync for robot
plugins. Run repository sync in admin dashboard after deployment to
populate plugin store.

Closes: Plugin store repository integration Resolves: Installation state
detection and repository name display Fixes: Admin authentication and
TypeScript compliance issues
This commit is contained in:
2025-08-07 10:47:29 -04:00
parent b1f4eedb53
commit 18f709f879
33 changed files with 5146 additions and 2273 deletions

323
scripts/test-seed-data.ts Normal file
View File

@@ -0,0 +1,323 @@
#!/usr/bin/env tsx
/**
* Test script to validate seed data structure
* Ensures all user relationships and study memberships are correct
*/
interface User {
id: string;
name: string;
email: string;
institution: string;
}
interface UserRole {
userId: string;
role: "administrator" | "researcher" | "wizard" | "observer";
assignedBy: string;
}
interface Study {
id: string;
name: string;
createdBy: string;
}
interface StudyMember {
studyId: string;
userId: string;
role: "owner" | "researcher" | "wizard" | "observer";
invitedBy: string | null;
}
function validateSeedData() {
console.log("🧪 Testing seed data structure...\n");
// Users data
const users: User[] = [
{
id: "01234567-89ab-cdef-0123-456789abcde0",
name: "Sean O'Connor",
email: "sean@soconnor.dev",
institution: "HRIStudio",
},
{
id: "01234567-89ab-cdef-0123-456789abcde1",
name: "Dr. Sarah Chen",
email: "sarah.chen@university.edu",
institution: "MIT Computer Science",
},
{
id: "01234567-89ab-cdef-0123-456789abcde2",
name: "Dr. Michael Rodriguez",
email: "m.rodriguez@research.org",
institution: "Stanford HCI Lab",
},
{
id: "01234567-89ab-cdef-0123-456789abcde3",
name: "Emma Thompson",
email: "emma.thompson@university.edu",
institution: "MIT Computer Science",
},
{
id: "01234567-89ab-cdef-0123-456789abcde4",
name: "Dr. James Wilson",
email: "james.wilson@university.edu",
institution: "MIT Computer Science",
},
];
// User roles
const userRoles: UserRole[] = [
{
userId: "01234567-89ab-cdef-0123-456789abcde0",
role: "administrator",
assignedBy: "01234567-89ab-cdef-0123-456789abcde0",
},
{
userId: "01234567-89ab-cdef-0123-456789abcde1",
role: "researcher",
assignedBy: "01234567-89ab-cdef-0123-456789abcde0",
},
{
userId: "01234567-89ab-cdef-0123-456789abcde2",
role: "researcher",
assignedBy: "01234567-89ab-cdef-0123-456789abcde0",
},
{
userId: "01234567-89ab-cdef-0123-456789abcde3",
role: "wizard",
assignedBy: "01234567-89ab-cdef-0123-456789abcde0",
},
{
userId: "01234567-89ab-cdef-0123-456789abcde4",
role: "observer",
assignedBy: "01234567-89ab-cdef-0123-456789abcde0",
},
];
// Studies
const studies: Study[] = [
{
id: "11234567-89ab-cdef-0123-456789abcde1",
name: "Robot Navigation Assistance Study",
createdBy: "01234567-89ab-cdef-0123-456789abcde0",
},
{
id: "11234567-89ab-cdef-0123-456789abcde2",
name: "Social Robots in Healthcare Study",
createdBy: "01234567-89ab-cdef-0123-456789abcde0",
},
{
id: "11234567-89ab-cdef-0123-456789abcde3",
name: "Elderly Care Robot Interaction Study",
createdBy: "01234567-89ab-cdef-0123-456789abcde0",
},
];
// Study members
const studyMembers: StudyMember[] = [
// Sean as owner of all studies
{
studyId: "11234567-89ab-cdef-0123-456789abcde1",
userId: "01234567-89ab-cdef-0123-456789abcde0",
role: "owner",
invitedBy: null,
},
{
studyId: "11234567-89ab-cdef-0123-456789abcde2",
userId: "01234567-89ab-cdef-0123-456789abcde0",
role: "owner",
invitedBy: null,
},
{
studyId: "11234567-89ab-cdef-0123-456789abcde3",
userId: "01234567-89ab-cdef-0123-456789abcde0",
role: "owner",
invitedBy: null,
},
// Other team members
{
studyId: "11234567-89ab-cdef-0123-456789abcde1",
userId: "01234567-89ab-cdef-0123-456789abcde2",
role: "researcher",
invitedBy: "01234567-89ab-cdef-0123-456789abcde0",
},
{
studyId: "11234567-89ab-cdef-0123-456789abcde1",
userId: "01234567-89ab-cdef-0123-456789abcde3",
role: "wizard",
invitedBy: "01234567-89ab-cdef-0123-456789abcde0",
},
{
studyId: "11234567-89ab-cdef-0123-456789abcde2",
userId: "01234567-89ab-cdef-0123-456789abcde2",
role: "researcher",
invitedBy: "01234567-89ab-cdef-0123-456789abcde0",
},
{
studyId: "11234567-89ab-cdef-0123-456789abcde3",
userId: "01234567-89ab-cdef-0123-456789abcde1",
role: "researcher",
invitedBy: "01234567-89ab-cdef-0123-456789abcde0",
},
];
let errors = 0;
console.log("👥 Validating users...");
console.log(` Users: ${users.length}`);
// Check for Sean as admin
const seanUser = users.find((u) => u.email === "sean@soconnor.dev");
if (seanUser) {
console.log(` ✅ Sean found: ${seanUser.name} (${seanUser.email})`);
} else {
console.error(` ❌ Sean not found as user`);
errors++;
}
console.log("\n🔐 Validating user roles...");
console.log(` User roles: ${userRoles.length}`);
// Check Sean's admin role
const seanRole = userRoles.find(
(r) => r.userId === "01234567-89ab-cdef-0123-456789abcde0",
);
if (seanRole && seanRole.role === "administrator") {
console.log(` ✅ Sean has administrator role`);
} else {
console.error(` ❌ Sean missing administrator role`);
errors++;
}
// Check all roles are assigned by Sean
const rolesAssignedBySean = userRoles.filter(
(r) => r.assignedBy === "01234567-89ab-cdef-0123-456789abcde0",
);
console.log(
`${rolesAssignedBySean.length}/${userRoles.length} roles assigned by Sean`,
);
console.log("\n📚 Validating studies...");
console.log(` Studies: ${studies.length}`);
// Check all studies created by Sean
const studiesCreatedBySean = studies.filter(
(s) => s.createdBy === "01234567-89ab-cdef-0123-456789abcde0",
);
if (studiesCreatedBySean.length === studies.length) {
console.log(` ✅ All ${studies.length} studies created by Sean`);
} else {
console.error(
` ❌ Only ${studiesCreatedBySean.length}/${studies.length} studies created by Sean`,
);
errors++;
}
console.log("\n👨💼 Validating study memberships...");
console.log(` Study memberships: ${studyMembers.length}`);
// Check Sean is owner of all studies
const seanOwnerships = studyMembers.filter(
(m) =>
m.userId === "01234567-89ab-cdef-0123-456789abcde0" && m.role === "owner",
);
if (seanOwnerships.length === studies.length) {
console.log(` ✅ Sean is owner of all ${studies.length} studies`);
} else {
console.error(
` ❌ Sean only owns ${seanOwnerships.length}/${studies.length} studies`,
);
errors++;
}
// Check invitation chain
const membersInvitedBySean = studyMembers.filter(
(m) => m.invitedBy === "01234567-89ab-cdef-0123-456789abcde0",
);
console.log(`${membersInvitedBySean.length} members invited by Sean`);
// Validate all user references exist
console.log("\n🔗 Validating references...");
const userIds = new Set(users.map((u) => u.id));
for (const role of userRoles) {
if (!userIds.has(role.userId)) {
console.error(` ❌ Invalid user reference in role: ${role.userId}`);
errors++;
}
if (!userIds.has(role.assignedBy)) {
console.error(
` ❌ Invalid assignedBy reference in role: ${role.assignedBy}`,
);
errors++;
}
}
for (const study of studies) {
if (!userIds.has(study.createdBy)) {
console.error(
` ❌ Invalid createdBy reference in study: ${study.createdBy}`,
);
errors++;
}
}
const studyIds = new Set(studies.map((s) => s.id));
for (const member of studyMembers) {
if (!studyIds.has(member.studyId)) {
console.error(
` ❌ Invalid study reference in membership: ${member.studyId}`,
);
errors++;
}
if (!userIds.has(member.userId)) {
console.error(
` ❌ Invalid user reference in membership: ${member.userId}`,
);
errors++;
}
if (member.invitedBy && !userIds.has(member.invitedBy)) {
console.error(
` ❌ Invalid invitedBy reference in membership: ${member.invitedBy}`,
);
errors++;
}
}
if (errors === 0) {
console.log(" ✅ All references are valid");
}
// Summary
console.log(`\n📊 Validation Summary:`);
console.log(` Users: ${users.length}`);
console.log(` User roles: ${userRoles.length}`);
console.log(` Studies: ${studies.length}`);
console.log(` Study memberships: ${studyMembers.length}`);
console.log(` Errors: ${errors}`);
if (errors === 0) {
console.log(`\n🎉 All validations passed! Seed data structure is correct.`);
console.log(` Sean (sean@soconnor.dev) is admin of everything:`);
console.log(` • System administrator role`);
console.log(` • Owner of all ${studies.length} studies`);
console.log(` • Assigned all user roles`);
console.log(` • Invited all study members`);
process.exit(0);
} else {
console.log(`\n❌ Validation failed with ${errors} error(s).`);
process.exit(1);
}
}
// Run the validation
if (import.meta.url === `file://${process.argv[1]}`) {
validateSeedData();
}
export { validateSeedData };