Skip to content

Commit aaf6c19

Browse files
authored
Merge pull request #2049 from trillium/ts.2047
feat(scripts): add modular project deletion script
2 parents ce31c4c + 288e4b1 commit aaf6c19

File tree

8 files changed

+1093
-0
lines changed

8 files changed

+1093
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Delete Project Script - Modular Structure
2+
3+
This directory contains the modular implementation of the project deletion script.
4+
5+
## File Structure
6+
7+
```
8+
deleteProject/
9+
├── README.md # This file
10+
├── index.js # Main orchestration file (entry point)
11+
├── config.js # Configuration constants (database names)
12+
├── utils.js # Utility functions (validation, CLI parsing, help)
13+
├── finders.js # Database query functions (find related records)
14+
├── displays.js # Display functions (show detailed information)
15+
└── deleters.js # Deletion functions (delete/update operations)
16+
```
17+
18+
## Module Descriptions
19+
20+
### `index.js`
21+
22+
Main orchestration file that coordinates the entire deletion process.
23+
24+
- Parses command-line arguments
25+
- Validates input and environment
26+
- Connects to MongoDB
27+
- Orchestrates the find → display → delete workflow
28+
- Handles errors and cleanup
29+
30+
### `config.js`
31+
32+
Configuration constants used throughout the script.
33+
34+
- Database names (production, development, test)
35+
- Can be extended with other configuration values
36+
37+
### `utils.js`
38+
39+
Utility and helper functions.
40+
41+
- `checkEnv()` - Validates required environment variables
42+
- `isValidObjectId()` - Validates MongoDB ObjectId format
43+
- `getProjectIdFromArgs()` - Extracts project ID from CLI arguments
44+
- `printHelp()` - Displays usage information
45+
46+
### `finders.js`
47+
48+
Database query functions for finding related records.
49+
50+
- `findProject()` - Find the project by ID
51+
- `findProjectEvents()` - Find all events for the project
52+
- `findEventCheckIns()` - Find all check-ins for project events
53+
- `findProjectTeamMembers()` - Find team members
54+
- `findUsersReferencingProject()` - Find users with project references
55+
- `findRelatedRecurringEvents()` - Find recurring events
56+
57+
### `displays.js`
58+
59+
Display functions for showing detailed record information.
60+
61+
- `displayUserDetails()` - Show user information
62+
- `displayEventDetails()` - Show event information
63+
- `displayCheckInDetails()` - Show check-in information (with user lookup)
64+
- `displayProjectTeamMemberDetails()` - Show team member information
65+
- `displayRecurringEventDetails()` - Show recurring event information
66+
67+
### `deleters.js`
68+
69+
Deletion and update functions.
70+
71+
- `deleteCheckIns()` - Delete check-in records
72+
- `deleteProjectTeamMembers()` - Delete team member records
73+
- `updateUsersRemoveProject()` - Remove project references from users
74+
- `deleteEvents()` - Delete event records
75+
- `deleteRecurringEvents()` - Delete recurring event records
76+
- `deleteProject()` - Delete the project itself
77+
78+
## Usage
79+
80+
### From the deleteProject directory:
81+
82+
```bash
83+
node index.js --project-id=<PROJECT_ID> [options]
84+
```
85+
86+
### From the scripts directory (backwards compatible):
87+
88+
```bash
89+
node deleteProjectAndAssociatedRecords.js --project-id=<PROJECT_ID> [options]
90+
```
91+
92+
### Options:
93+
94+
- `--project-id=<ID>` - MongoDB ObjectId of the project to delete (REQUIRED)
95+
- `--prod` - Operate on production database
96+
- `--live` - Operate on development/staging database
97+
- `--test` - Operate on test database
98+
- `--mock` - Dry run (no actual deletion)
99+
- `--execute` - Execute the deletion
100+
- `--help` - Show help message
101+
102+
## Examples
103+
104+
```bash
105+
# Dry run on production
106+
node index.js --project-id=644748563212e6001fbca24a --prod --mock
107+
108+
# Execute on test database
109+
node index.js --project-id=644748563212e6001fbca24a --test --execute
110+
111+
# Execute on production (with 5-second warning)
112+
node index.js --project-id=644748563212e6001fbca24a --prod --execute
113+
```
114+
115+
## Safety Features
116+
117+
1. **Mock mode** - Preview all changes before executing
118+
2. **5-second warning** - Countdown for production/live databases
119+
3. **Validation** - Validates project ID and environment variables
120+
4. **Detailed output** - Shows exactly what will be deleted
121+
5. **Orphan detection** - Identifies orphaned check-ins
122+
123+
## Deletion Order
124+
125+
The script follows this order to maintain referential integrity:
126+
127+
1. Check-ins
128+
2. Project team members
129+
3. User references (updated, not deleted)
130+
4. Events
131+
5. Recurring events
132+
6. Project
133+
134+
## Environment Requirements
135+
136+
- `MIGRATION_DB_URI` - MongoDB connection string
137+
138+
Load from `backend/.env` file.
139+
140+
## See Also
141+
142+
- Main documentation: `tmp/GUIDE.md`
143+
- Manual deletion steps for MongoDB Compass users
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Configuration constants for project deletion script
3+
*/
4+
5+
// Database names
6+
const PROD_DB_NAME = 'db'; // Actual production database
7+
const DEV_DB_NAME = 'vrms-test'; // Development/staging database
8+
const DEV_TEST_DB_NAME = 'vrms-populate-projects-test'; // Test database for migration
9+
10+
module.exports = {
11+
PROD_DB_NAME,
12+
DEV_DB_NAME,
13+
DEV_TEST_DB_NAME,
14+
};
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/**
2+
* Deletion functions for removing project-related records
3+
*/
4+
5+
const { ObjectId } = require('mongodb');
6+
7+
/**
8+
* Delete check-ins
9+
* @param {Db} db - MongoDB database instance
10+
* @param {Array} checkIns - Array of check-in documents
11+
* @param {boolean} isMock - If true, only print what would be deleted
12+
* @returns {Promise<void>}
13+
*/
14+
async function deleteCheckIns(db, checkIns, isMock) {
15+
if (checkIns.length === 0) {
16+
console.log('[INFO] No check-ins to delete.');
17+
return;
18+
}
19+
20+
if (isMock) {
21+
console.log(`[MOCK] Would delete ${checkIns.length} check-in(s).`);
22+
return;
23+
}
24+
25+
const checkInIds = checkIns.map((ci) => ci._id);
26+
const result = await db.collection('checkins').deleteMany({ _id: { $in: checkInIds } });
27+
28+
console.log(`[SUCCESS] Deleted ${result.deletedCount} check-in(s).`);
29+
}
30+
31+
/**
32+
* Delete project team members
33+
* @param {Db} db - MongoDB database instance
34+
* @param {string} projectId - The project ID
35+
* @param {boolean} isMock - If true, only print what would be deleted
36+
* @returns {Promise<void>}
37+
*/
38+
async function deleteProjectTeamMembers(db, projectId, isMock) {
39+
const count = await db.collection('projectteammembers').countDocuments({ projectId: projectId });
40+
41+
if (count === 0) {
42+
console.log('[INFO] No project team members to delete.');
43+
return;
44+
}
45+
46+
if (isMock) {
47+
console.log(`[MOCK] Would delete ${count} project team member(s).`);
48+
return;
49+
}
50+
51+
const result = await db.collection('projectteammembers').deleteMany({ projectId: projectId });
52+
53+
console.log(`[SUCCESS] Deleted ${result.deletedCount} project team member(s).`);
54+
}
55+
56+
/**
57+
* Update users to remove project references
58+
* @param {Db} db - MongoDB database instance
59+
* @param {string} projectId - The project ID
60+
* @param {Array} users - Array of user documents
61+
* @param {boolean} isMock - If true, only print what would be updated
62+
* @returns {Promise<void>}
63+
*/
64+
async function updateUsersRemoveProject(db, projectId, users, isMock) {
65+
if (users.length === 0) {
66+
console.log('[INFO] No users to update.');
67+
return;
68+
}
69+
70+
if (isMock) {
71+
console.log(`[MOCK] Would update ${users.length} user(s) to remove project references.`);
72+
users.forEach((user) => {
73+
const hasInProjects = user.projects?.some((pid) => String(pid) === projectId);
74+
const hasInManagedProjects = user.managedProjects?.includes(projectId);
75+
console.log(` - User: ${user.name?.firstName} ${user.name?.lastName} (${user.email})`);
76+
if (hasInProjects) console.log(` * Remove from 'projects' array`);
77+
if (hasInManagedProjects) console.log(` * Remove from 'managedProjects' array`);
78+
});
79+
return;
80+
}
81+
82+
const operations = users.map((user) => ({
83+
updateOne: {
84+
filter: { _id: user._id },
85+
update: {
86+
$pull: {
87+
projects: new ObjectId(projectId),
88+
managedProjects: projectId,
89+
},
90+
},
91+
},
92+
}));
93+
94+
const result = await db.collection('users').bulkWrite(operations, { ordered: false });
95+
96+
console.log(`[SUCCESS] Updated ${result.modifiedCount} user(s) to remove project references.`);
97+
}
98+
99+
/**
100+
* Delete events
101+
* @param {Db} db - MongoDB database instance
102+
* @param {string} projectId - The project ID
103+
* @param {boolean} isMock - If true, only print what would be deleted
104+
* @returns {Promise<void>}
105+
*/
106+
async function deleteEvents(db, projectId, isMock) {
107+
const count = await db.collection('events').countDocuments({ project: new ObjectId(projectId) });
108+
109+
if (count === 0) {
110+
console.log('[INFO] No events to delete.');
111+
return;
112+
}
113+
114+
if (isMock) {
115+
console.log(`[MOCK] Would delete ${count} event(s).`);
116+
return;
117+
}
118+
119+
const result = await db.collection('events').deleteMany({ project: new ObjectId(projectId) });
120+
121+
console.log(`[SUCCESS] Deleted ${result.deletedCount} event(s).`);
122+
}
123+
124+
/**
125+
* Delete recurring events
126+
* @param {Db} db - MongoDB database instance
127+
* @param {Array} recurringEvents - Array of recurring event documents
128+
* @param {boolean} isMock - If true, only print what would be deleted
129+
* @returns {Promise<void>}
130+
*/
131+
async function deleteRecurringEvents(db, recurringEvents, isMock) {
132+
if (recurringEvents.length === 0) {
133+
console.log('[INFO] No recurring events to delete.');
134+
return;
135+
}
136+
137+
if (isMock) {
138+
console.log(`[MOCK] Would delete ${recurringEvents.length} recurring event(s).`);
139+
return;
140+
}
141+
142+
const recurringEventIds = recurringEvents.map((re) => re._id);
143+
const result = await db
144+
.collection('recurringevents')
145+
.deleteMany({ _id: { $in: recurringEventIds } });
146+
147+
console.log(`[SUCCESS] Deleted ${result.deletedCount} recurring event(s).`);
148+
}
149+
150+
/**
151+
* Delete the project
152+
* @param {Db} db - MongoDB database instance
153+
* @param {string} projectId - The project ID
154+
* @param {boolean} isMock - If true, only print what would be deleted
155+
* @returns {Promise<void>}
156+
*/
157+
async function deleteProject(db, projectId, isMock) {
158+
if (isMock) {
159+
console.log(`[MOCK] Would delete project: ${projectId}`);
160+
return;
161+
}
162+
163+
const result = await db.collection('projects').deleteOne({ _id: new ObjectId(projectId) });
164+
165+
console.log(`[SUCCESS] Deleted ${result.deletedCount} project.`);
166+
}
167+
168+
module.exports = {
169+
deleteCheckIns,
170+
deleteProjectTeamMembers,
171+
updateUsersRemoveProject,
172+
deleteEvents,
173+
deleteRecurringEvents,
174+
deleteProject,
175+
};

0 commit comments

Comments
 (0)