Skip to content

Commit 53d6efe

Browse files
committed
fix: Cosmos DB compatibility for tasks, sprints, and projects
- Fixed Task model: use countDocuments instead of sort for taskKey generation - Fixed Project model: added unique key field, fixed async pre-save hook - Fixed Sprint model: fixed async pre-save hook (removed next() callback) - Added Cosmos DB indexes to all models for sort operations - Updated seed script to use local backend URL with configurable port
1 parent 1c29b76 commit 53d6efe

File tree

8 files changed

+97
-29
lines changed

8 files changed

+97
-29
lines changed

backend/models/Activity.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,8 @@ const ActivitySchema = new mongoose.Schema({
2828
}
2929
});
3030

31+
// Index for Cosmos DB compatibility
32+
ActivitySchema.index({ targetType: 1, targetId: 1, createdAt: -1 });
33+
ActivitySchema.index({ user: 1, createdAt: -1 });
34+
3135
module.exports = mongoose.model('Activity', ActivitySchema);

backend/models/Attachment.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,7 @@ AttachmentSchema.virtual('formattedSize').get(function () {
4747

4848
AttachmentSchema.set('toJSON', { virtuals: true });
4949

50+
// Index for Cosmos DB compatibility
51+
AttachmentSchema.index({ task: 1, createdAt: -1 });
52+
5053
module.exports = mongoose.model('Attachment', AttachmentSchema);

backend/models/Comment.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ const CommentSchema = new mongoose.Schema({
3333
}
3434
});
3535

36+
// Index for Cosmos DB compatibility
37+
CommentSchema.index({ task: 1, createdAt: -1 });
38+
3639
// Update the updatedAt field on save
3740
CommentSchema.pre('save', function (next) {
3841
if (!this.isNew) {

backend/models/Project.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ const ProjectSchema = new mongoose.Schema({
66
required: [true, 'Please add a project name'],
77
trim: true
88
},
9+
key: {
10+
type: String,
11+
uppercase: true,
12+
trim: true,
13+
unique: true,
14+
sparse: true, // Allow multiple nulls during creation
15+
maxlength: [10, 'Project key cannot exceed 10 characters']
16+
},
917
description: {
1018
type: String,
1119
required: [true, 'Please add a description']
@@ -30,4 +38,23 @@ const ProjectSchema = new mongoose.Schema({
3038
}
3139
});
3240

41+
// Auto-generate key from project name if not provided
42+
ProjectSchema.pre('save', async function () {
43+
if (this.isNew && !this.key) {
44+
// Generate key from first 4 chars of name, uppercase, alphanumeric only
45+
let baseKey = this.name.substring(0, 4).toUpperCase().replace(/[^A-Z0-9]/g, '');
46+
if (baseKey.length === 0) baseKey = 'PROJ';
47+
48+
// Ensure uniqueness
49+
let key = baseKey;
50+
let suffix = 1;
51+
while (await this.constructor.findOne({ key })) {
52+
key = `${baseKey}${suffix}`;
53+
suffix++;
54+
}
55+
this.key = key;
56+
}
57+
});
58+
3359
module.exports = mongoose.model('Project', ProjectSchema);
60+

backend/models/Sprint.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,22 @@ const SprintSchema = new mongoose.Schema({
4848
}
4949
});
5050

51+
// Indexes for Cosmos DB compatibility - required for sort operations
52+
SprintSchema.index({ project: 1, startDate: -1 });
53+
SprintSchema.index({ project: 1, endDate: -1 });
54+
SprintSchema.index({ project: 1, status: 1 });
55+
5156
// Ensure only one active sprint per project
52-
SprintSchema.pre('save', async function (next) {
53-
try {
54-
if (this.status === 'active') {
55-
const existingActive = await this.constructor.findOne({
56-
project: this.project,
57-
status: 'active',
58-
_id: { $ne: this._id }
59-
});
60-
if (existingActive) {
61-
return next(new Error('Project already has an active sprint'));
62-
}
57+
SprintSchema.pre('save', async function () {
58+
if (this.status === 'active') {
59+
const existingActive = await this.constructor.findOne({
60+
project: this.project,
61+
status: 'active',
62+
_id: { $ne: this._id }
63+
});
64+
if (existingActive) {
65+
throw new Error('Project already has an active sprint');
6366
}
64-
next();
65-
} catch (error) {
66-
next(error);
6767
}
6868
});
6969

backend/models/Task.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,33 @@ const TaskSchema = new mongoose.Schema({
9090
}
9191
});
9292

93+
// Index for Cosmos DB compatibility - required for sort operations
94+
TaskSchema.index({ createdAt: -1 });
95+
TaskSchema.index({ project: 1, createdAt: -1 });
96+
TaskSchema.index({ sprint: 1, createdAt: -1 });
97+
TaskSchema.index({ taskKey: 1 });
98+
9399
// Generate task key before saving (e.g., PROJ-1, PROJ-2)
94100
TaskSchema.pre('save', async function () {
95101
if (this.isNew && !this.taskKey) {
96102
const Project = mongoose.model('Project');
97103
const project = await Project.findById(this.project);
104+
// Use project key if available, otherwise derive from name
105+
const prefix = project?.key || project?.name?.substring(0, 4).toUpperCase().replace(/[^A-Z0-9]/g, '') || 'TASK';
106+
107+
// Count existing tasks for this project and add 1
108+
// This avoids Cosmos DB sorting issues
98109
const count = await this.constructor.countDocuments({ project: this.project });
99-
const prefix = project?.name?.substring(0, 4).toUpperCase() || 'TASK';
100-
this.taskKey = `${prefix}-${count + 1}`;
110+
let nextNum = count + 1;
111+
112+
// Ensure uniqueness by checking if key exists and incrementing if needed
113+
let taskKey = `${prefix}-${nextNum}`;
114+
while (await this.constructor.findOne({ taskKey })) {
115+
nextNum++;
116+
taskKey = `${prefix}-${nextNum}`;
117+
}
118+
119+
this.taskKey = taskKey;
101120
}
102121
this.updatedAt = Date.now();
103122
});

backend/models/TimeLog.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,7 @@ const TimeLogSchema = new mongoose.Schema({
3030
}
3131
});
3232

33+
// Index for Cosmos DB compatibility
34+
TimeLogSchema.index({ task: 1, loggedAt: -1 });
35+
3336
module.exports = mongoose.model('TimeLog', TimeLogSchema);

seed-data.js

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Seed script for FlowOps - Run with: node seed-data.js
22

3-
const API_URL = 'https://flowops-backend.azurewebsites.net/api';
3+
// Change to localhost for local testing, or use Azure backend for production
4+
const API_URL = process.env.API_URL || 'http://localhost:3001/api';
5+
// const API_URL = 'https://flowops-backend.azurewebsites.net/api';
46

57
async function seedData() {
68
console.log('🌱 Seeding FlowOps database...\n');
@@ -148,20 +150,27 @@ async function seedData() {
148150

149151
// 5. Create sample sprints for first project
150152
console.log('\n5. Creating sample sprints...');
151-
153+
152154
if (createdProjects.length > 0) {
153155
const project = createdProjects[0];
154156
const projectId = project._id || project.id;
155-
156-
// Get tasks for this project to add to sprints
157-
const tasksRes = await fetch(`${API_URL}/projects/${projectId}/tasks`, { headers });
158-
const tasksData = await tasksRes.json();
159-
const projectTasks = tasksData.data || [];
160-
157+
158+
// Refresh tasks list for this project to ensure we have all tasks (newly created or existing)
159+
console.log(' 🔄 Fetching latest tasks for project...');
160+
let projectTasks = [];
161+
try {
162+
const tasksRes = await fetch(`${API_URL}/projects/${projectId}/tasks`, { headers });
163+
const tasksData = await tasksRes.json();
164+
projectTasks = tasksData.data || [];
165+
console.log(` ✅ Found ${projectTasks.length} tasks available for sprints`);
166+
} catch (e) {
167+
console.log(' ⚠️ Failed to fetch tasks:', e.message);
168+
}
169+
161170
const today = new Date();
162171
const twoWeeksFromNow = new Date(today.getTime() + 14 * 24 * 60 * 60 * 1000);
163172
const fourWeeksFromNow = new Date(today.getTime() + 28 * 24 * 60 * 60 * 1000);
164-
173+
165174
const sprints = [
166175
{
167176
name: 'Sprint 1 - Foundation',
@@ -176,7 +185,7 @@ async function seedData() {
176185
endDate: fourWeeksFromNow.toISOString()
177186
}
178187
];
179-
188+
180189
const createdSprints = [];
181190
for (const sprint of sprints) {
182191
try {
@@ -196,11 +205,11 @@ async function seedData() {
196205
console.log(` ❌ Failed: ${sprint.name} - ${e.message}`);
197206
}
198207
}
199-
208+
200209
// Start the first sprint and add tasks to it
201210
if (createdSprints.length > 0) {
202211
const firstSprint = createdSprints[0];
203-
212+
204213
// Add some tasks to the sprint
205214
const taskIdsForSprint = projectTasks.slice(0, 6).map(t => t._id);
206215
if (taskIdsForSprint.length > 0) {
@@ -215,7 +224,7 @@ async function seedData() {
215224
console.log(` ❌ Failed to add tasks to sprint`);
216225
}
217226
}
218-
227+
219228
// Start the sprint
220229
try {
221230
await fetch(`${API_URL}/sprints/${firstSprint._id}/start`, {

0 commit comments

Comments
 (0)