Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 123 additions & 48 deletions backend/src/api/activities.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,51 @@

import express from 'express';
import dalActivity from '../repository/dalActivity';
import dalResource from '../repository/dalResource';
import { ActivityModel } from '../models/Activity';
import dalBoard from '../repository/dalBoard'; // Needed to fetch board details
import { ActivityModel } from '../models/Activity'; // Ensure this model includes isActive?: boolean
import { ResourceClass as Resource } from '../models/Resource'; // Use the exported class alias
import { BoardModel as Board } from '../models/Board'; // Assuming BoardModel is the exported class name

const router = express.Router();

interface ActiveActivityDetailsResponse {
activityID: string;
boardID: string;
board: Board | null; // Type hint with the imported Board class/interface
resources: Resource[]; // Type hint with the imported Resource class/interface
name: string; // Name of the activity
// Add any other activity properties the roomcasting environment might need
}

// Create a new activity
router.post('/', async (req, res) => {
try {
const activityData: ActivityModel = req.body;
const newActivity = await dalActivity.create(activityData);
res.status(201).json(newActivity); // 201 Created status
const activityDataFromRequest: Partial<ActivityModel> = req.body;
const activityDataToCreate: ActivityModel = {
...activityDataFromRequest,
activityID: activityDataFromRequest.activityID || '',
name: activityDataFromRequest.name || '',
projectID: activityDataFromRequest.projectID || '',
boardID: activityDataFromRequest.boardID || '',
groupIDs: activityDataFromRequest.groupIDs || [],
order: activityDataFromRequest.order === undefined ? 0 : activityDataFromRequest.order,
isActive: activityDataFromRequest.isActive === undefined ? false : activityDataFromRequest.isActive,
} as ActivityModel;

if (!activityDataToCreate.projectID || !activityDataToCreate.boardID || !activityDataToCreate.name) {
return res.status(400).json({ error: 'Missing required fields (projectID, boardID, name) for activity.' });
}

// If creating an activity and setting it to active, ensure others in the same project are inactive
if (activityDataToCreate.isActive === true && activityDataToCreate.projectID) {
await dalActivity.deactivateOtherActivitiesInProject(activityDataToCreate.projectID, null); // Pass null if no activityID to exclude
}

const newActivity = await dalActivity.create(activityDataToCreate);
res.status(201).json(newActivity);
} catch (error) {
console.error("Error creating activity:", error);
res.status(500).json({ error: 'Failed to create activity.' });
res.status(500).json({ error: 'Failed to create activity.' });
}
});

Expand All @@ -25,68 +56,112 @@ router.get('/project/:projectID', async (req, res) => {
const activities = await dalActivity.getByProject(projectID);
res.status(200).json(activities);
} catch (error) {
console.error("Error fetching activities:", error);
console.error("Error fetching activities for project:", error);
res.status(500).json({ error: 'Failed to fetch activities.' });
}
});

// Update an activity
// Get the single active activity for a project, along with its resources and board details
router.get('/project/:projectID/active-details', async (req, res) => {
try {
const projectID = req.params.projectID;
const activeActivity = await dalActivity.findActiveByProject(projectID);

if (!activeActivity) {
return res.status(200).json(null); // No active activity, return null
}

const resources = await dalResource.getByActivity(activeActivity.activityID);
const board = await dalBoard.getById(activeActivity.boardID);

const responseData: ActiveActivityDetailsResponse = {
activityID: activeActivity.activityID,
boardID: activeActivity.boardID,
board: board, // Include full board object
resources: resources || [],
name: activeActivity.name,
};

res.status(200).json(responseData);
} catch (error) {
console.error("Error fetching active activity details for project:", error);
res.status(500).json({ error: 'Failed to fetch active activity details.' });
}
});


// Update an activity (this now handles setting one active and others inactive)
router.put('/:activityID', async (req, res) => {
try {
const activityID = req.params.activityID;
const activityIDToUpdate = req.params.activityID;
const updatedData: Partial<ActivityModel> = req.body;

// If groupIDs are modified, update the resources
if (updatedData.groupIDs) {
const originalGroupIDs = (await dalActivity.getById(activityID))?.groupIDs;
if (updatedData.isActive === true) {
const activityToActivate = await dalActivity.getById(activityIDToUpdate);
if (!activityToActivate) {
return res.status(404).json({ error: 'Activity to activate not found.' });
}
// Deactivate other activities in the same project
await dalActivity.deactivateOtherActivitiesInProject(activityToActivate.projectID, activityIDToUpdate);
}

if (updatedData.groupIDs) {
const activity = await dalActivity.getById(activityIDToUpdate);
if (!activity) {
return res.status(404).json({ error: 'Activity not found for groupID update.' });
}
const originalGroupIDs = activity.groupIDs;
const removedGroupIDs = originalGroupIDs?.filter(id => !updatedData.groupIDs?.includes(id)) || [];
const removePromises = removedGroupIDs.map(groupID =>
dalResource.removeGroupFromActivityResources(activityID, groupID)
);
await Promise.all(removePromises);
if (removedGroupIDs.length > 0) {
const removePromises = removedGroupIDs.map(groupID =>
dalResource.removeGroupFromActivityResources(activityIDToUpdate, groupID)
);
await Promise.all(removePromises);
}
}

const updatedActivity = await dalActivity.update(activityID, updatedData);
const updatedActivity = await dalActivity.update(activityIDToUpdate, updatedData);

if (updatedActivity) {
res.status(200).json(updatedActivity);
} else {
res.status(404).json({ error: 'Activity not found.' });
res.status(404).json({ error: 'Activity not found or failed to update.' });
}
} catch (error) {
console.error("Error updating activity:", error);
res.status(500).json({ error: 'Failed to update activity.' });
}
});

// Delete an activity
router.delete('/:activityID', async (req, res) => {
try {
const activityID = req.params.activityID;
const deletedActivity = await dalActivity.remove(activityID);
if (deletedActivity) {
res.status(200).json(deletedActivity);
} else {
res.status(404).json({ error: 'Activity not found.' });
}
} catch (error) {
console.error("Error deleting activity:", error);
res.status(500).json({ error: 'Failed to delete activity.' });
}
});

router.post('/order', async (req, res) => {
try {
const activities: { activityID: string; order: number }[] = req.body.activities;
const updatePromises = activities.map(activity =>
dalActivity.update(activity.activityID, { order: activity.order })
);
await Promise.all(updatePromises);
res.status(200).json({ message: 'Activity order updated successfully.' });
} catch (error) {
console.error("Error updating activity order:", error);
res.status(500).json({ error: 'Failed to update activity order.' });
// Delete an activity
router.delete('/:activityID', async (req, res) => {
try {
const activityID = req.params.activityID;
const deletedActivity = await dalActivity.remove(activityID);
if (deletedActivity) {
res.status(200).json(deletedActivity);
} else {
res.status(404).json({ error: 'Activity not found.' });
}
});

export default router;
} catch (error) {
console.error("Error deleting activity:", error);
res.status(500).json({ error: 'Failed to delete activity.' });
}
});

// Update activity order
router.post('/order', async (req, res) => {
try {
const activities: { activityID: string; order: number }[] = req.body.activities;
const updatePromises = activities.map(activity =>
dalActivity.update(activity.activityID, { order: activity.order })
);
await Promise.all(updatePromises);
res.status(200).json({ message: 'Activity order updated successfully.' });
} catch (error) {
console.error("Error updating activity order:", error);
res.status(500).json({ error: 'Failed to update activity order.' });
}
});

export default router;
82 changes: 50 additions & 32 deletions backend/src/api/projects.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Router } from 'express';
import { mongo } from 'mongoose';
import { BoardScope, ViewSettings, ViewType } from '../models/Board';
import { ProjectModel } from '../models/Project';
import { ProjectModel } from '../models/Project';
import { UserModel } from '../models/User';
import dalBoard from '../repository/dalBoard';
import dalProject from '../repository/dalProject';
Expand All @@ -17,22 +17,34 @@ import { addUserToProject } from '../utils/project.helpers';
const router = Router();

router.post('/', async (req, res) => {
const project: ProjectModel = req.body;
const projectDataFromRequest: ProjectModel = req.body;
const user: UserModel = res.locals.user;

if (!project.teacherIDs.includes(user.userID)) {
if (!projectDataFromRequest.teacherIDs || !projectDataFromRequest.teacherIDs.includes(user.userID)) {
return res.status(403).end('Unauthorized to create project.');
}

// Set isScoreRun to false if not provided
project.isScoreRun = project.isScoreRun || false;
const projectToCreate: ProjectModel = {
...projectDataFromRequest,
isScoreRun: projectDataFromRequest.isScoreRun || false,
// currentActivePhase is removed from the model, so no initialization needed here
boards: projectDataFromRequest.boards || [],
groups: projectDataFromRequest.groups || [],
members: projectDataFromRequest.members || [user.userID],
teacherIDs: projectDataFromRequest.teacherIDs || [user.userID],
};

let savedProject = await dalProject.create(projectToCreate);
if (!savedProject) {
console.error("Failed to create project in DAL.");
return res.status(500).json({ error: 'Project creation failed at data access layer.' });
}

let savedProject = await dalProject.create(project);
if (project.personalBoardSetting.enabled) {
const image = project.personalBoardSetting.bgImage;
if (projectToCreate.personalBoardSetting && projectToCreate.personalBoardSetting.enabled) {
const image = projectToCreate.personalBoardSetting.bgImage;
const personalBoardID = new mongo.ObjectId().toString();
const personalBoard = await dalBoard.create({
projectID: project.projectID,
projectID: savedProject.projectID,
boardID: personalBoardID,
ownerID: user.userID,
name: `${user.username}'s Personal Board`,
Expand All @@ -48,33 +60,36 @@ router.post('/', async (req, res) => {
defaultView: ViewType.CANVAS,
viewSettings: getAllViewsAllowed(),
});
await savedProject.updateOne({
boards: [personalBoard.boardID],
});

// --- Create default shared board ---
const communityBoardID = new mongo.ObjectId().toString();
const communityBoard = await dalBoard.create({
projectID: project.projectID,
projectID: savedProject.projectID,
boardID: communityBoardID,
ownerID: user.userID,
name: 'Demo Community Board', // Or any default name you prefer
name: 'Demo Community Board',
scope: BoardScope.PROJECT_SHARED,
task: undefined,
permissions: getDefaultBoardPermissions(),
bgImage: undefined,
type: BoardType.BRAINSTORMING, // Or another default type
type: BoardType.BRAINSTORMING,
tags: getDefaultBoardTags(communityBoardID),
initialZoom: 100,
upvoteLimit: 5,
visible: true,
defaultView: ViewType.CANVAS,
viewSettings: getAllViewsAllowed(),
});
// Add the board to the project's boards array
savedProject = await savedProject.updateOne({
$push: { boards: communityBoard.boardID },

const boardsToUpdate = [personalBoard.boardID, communityBoard.boardID];
const projectUpdateWithBoards = await dalProject.update(savedProject.projectID, {
boards: boardsToUpdate,
});

if (!projectUpdateWithBoards) {
console.error(`Failed to update project ${savedProject.projectID} with initial boards.`);
return res.status(500).json({ error: 'Project created, but failed to link initial boards.' });
}
savedProject = projectUpdateWithBoards;
}

res.status(200).json(savedProject);
Expand All @@ -83,7 +98,6 @@ router.post('/', async (req, res) => {
router.post('/join', async (req, res) => {
const { code } = req.body;
const user: UserModel = res.locals.user;

try {
const project = await addUserToProject(user, code);
return res.status(200).json(project);
Expand All @@ -97,36 +111,40 @@ router.post('/join', async (req, res) => {
router.post('/:id', async (req, res) => {
const id = req.params.id;
const { name, members, boards, membershipDisabled } = req.body;

const project: Partial<ProjectModel> = Object.assign(
{},
name === null ? null : { name },
members === null ? null : { members },
boards === null ? null : { boards },
membershipDisabled === null ? null : { membershipDisabled }
);

const updatedProject = await dalProject.update(id, project);
const projectUpdate: Partial<ProjectModel> = {};
if (name !== undefined) projectUpdate.name = name;
if (members !== undefined) projectUpdate.members = members;
if (boards !== undefined) projectUpdate.boards = boards;
if (membershipDisabled !== undefined) projectUpdate.membershipDisabled = membershipDisabled;

const updatedProject = await dalProject.update(id, projectUpdate);
if (!updatedProject) {
return res.status(404).json({ error: 'Project not found or failed to update.' });
}
res.status(200).json(updatedProject);
});

router.get('/:id', async (req, res) => {
const id = req.params.id;

const project = await dalProject.getById(id);
if (!project) {
return res.status(404).json({ error: 'Project not found.' });
}
res.status(200).json(project);
});

router.get('/users/:id', async (req, res) => {
const id = req.params.id;

const projects = await dalProject.getByUserId(id);
res.status(200).json(projects);
});

router.delete('/:id', async (req, res) => {
const { id } = req.params;
const deletedProject = await dalProject.remove(id);
if (!deletedProject) {
return res.status(404).json({ error: 'Project not found.' });
}
res.status(200).json(deletedProject);
});

Expand Down
1 change: 1 addition & 0 deletions backend/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export enum SocketEvent {
AI_RESPONSE = 'AI_RESPONSE', // Event for receiving AI response

RESOURCES_UPDATE = 'RESOURCES_UPDATE',
ACTIVITY_STOPPED = 'ACTIVITY_STOPPED',
}

export const STUDENT_POST_COLOR = '#FFF7C0';
Expand Down
Loading
Loading