Skip to content

Commit

Permalink
Merge pull request #460 from cornell-dti/jacqueline/db-descriptions
Browse files Browse the repository at this point in the history
Updating Course Descriptions
  • Loading branch information
jacquelinecai authored Oct 26, 2024
2 parents e9fbd8c + 09efb03 commit c9f07e3
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 4 deletions.
31 changes: 29 additions & 2 deletions client/src/modules/Admin/Components/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const Admin = () => {
const [doubleClick, setDoubleClick] = useState<boolean>(false)

const [updating, setUpdating] = useState<boolean>(false);
type updatedStates = 'empty' | 'semester' | 'profsReset' | 'profsUpdate' | 'subjects' | 'database';
type updatedStates = 'empty' | 'semester' | 'profsReset' | 'profsUpdate' | 'subjects' | 'database' | 'description';
const [updated, setUpdated] = useState<updatedStates>('empty');
const successMessages = {
'empty': '',
Expand All @@ -32,6 +32,7 @@ export const Admin = () => {
'profsUpdate': "Professor data successfully updated",
'subjects': "Subject full name data successfully updated",
'database': "Database successfully initialized",
'description': "Course description data successfully added"
};
const [updatingField, setUpdatingField] = useState<string>("");

Expand Down Expand Up @@ -249,6 +250,24 @@ export const Admin = () => {
}

/**
* Call when user selects "Update Descriptions" button. Scrapes the Course API
* to retrieve course description and stores them in the Course database.
*/
async function updateDescriptions() {
console.log('Updating course descriptions')
setUpdating(true)
setUpdatingField("course descriptions")
const response = await axios.post('/api/admin/course/desc', { token: token });
if (response.status === 200) {
console.log('Updated all course descriptions')
setUpdating(false)
setUpdated('description')
} else {
console.log('Error at updateDescriptions')
}
}

/*
* Call when admin wants to update the list of subjects users can search through
* when clicking the "Update Subjects" button
*/
Expand Down Expand Up @@ -353,6 +372,14 @@ export const Admin = () => {
>
Reset Professors
</button>
<button
disabled={updating}
type="button"
className={styles.adminButtons}
onClick={() => updateDescriptions()}
>
Update Descriptions
</button>
<button
disabled={updating}
type="button"
Expand Down Expand Up @@ -420,7 +447,7 @@ export const Admin = () => {
})}
</div>
</div>
</div>
</div >
)
}

Expand Down
1 change: 1 addition & 0 deletions common/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface Class {
classSub: string;
classNum: string;
classTitle?: string;
classDescription?: string;
classPrereq: string[];
crossList: string[];
classFull?: string;
Expand Down
1 change: 1 addition & 0 deletions server/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const ClassSchema = new Schema<ClassDocument>({
classSub: { type: String }, // subject, like "PHIL" or "CS"
classNum: { type: String }, // course number, like 1110
classTitle: { type: String }, // class title, like 'Introduction to Algorithms'
classDescription: { type: String }, // class description from Course API
classPrereq: { type: [String], required: false }, // list of pre-req classes, a string of Classes _id.
crossList: { type: [String], required: false }, // list of classes that are crosslisted with this one, a string of Classes _id.
classFull: { type: String }, // full class title to search by, formated as 'classSub classNum: classTitle'
Expand Down
1 change: 1 addition & 0 deletions server/scripts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export {
addCrossList,
addNewSemester,
addAllCourses,
addAllDescriptions,
} from './populate-courses';

export { addAllProfessors, resetProfessors } from './populate-professors';
Expand Down
115 changes: 115 additions & 0 deletions server/scripts/populate-courses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ScrapingSubject, ScrapingClass } from './types';
import { Classes, Professors, Subjects } from '../db/schema';
import { extractProfessors } from './populate-professors';
import { fetchSubjects } from './populate-subjects';
import { addStudentReview } from '../src/review/review.controller';

/**
* Adds all possible crosslisted classes retrieved from Course API to crosslisted list in Courses database for all semesters.
Expand Down Expand Up @@ -454,3 +455,117 @@ export const addAllCourses = async (
console.log('Finished addAllCourses');
return true;
};

/**
* Helper function that removes invalid entries from the database based on the updated Course API
* @param course the course that includes an invalid entry
* @param semester the invalid semester, must exist in the classSems list of course
*
* @returns true if operation was successful, false otherwise
*/
const removeInvalidSem = async (course, semester): Promise<boolean> => {
try {
const semesters = (course.classSems).filter(sem => sem !== semester);
const courseId = course._id;
await Classes.updateOne(
{ _id: courseId },
{ $set: { classSems: semesters } },
);
return true;
} catch (err) {
console.log(`Error occurred in removing invalid entry in database: ${err}`)
}
return false;
}

/**
* Helper function that removes a course from the database
* @param course the course that is being removed
*
* @returns true if operation was successful, false otherwise
*/
const removeCourse = async (course): Promise<boolean> => {
const subject = course.classSub.toUpperCase();
const num = course.classNum;
try {
const courseId = course._id;
const result = await Classes.deleteOne({ _id: courseId });
if (result.deletedCount === 1) {
console.log(`Course ${subject} ${num} successfully removed from database.`);
return true;
} else {
console.log(`Course ${subject} ${num} was not found in the database.`);
}
} catch (err) {
console.log(`Failed to remove course ${subject} ${num} from database due to ${err}.`);
}
return false;
}

/**
* Adds all course descriptions for the most recent semester from Course API to each class in Course database.
* Called after adding all new courses and professors for a new semester.
*
* @returns true if operation was successful, false otherwise
*/
export const addAllDescriptions = async (): Promise<boolean> => {
try {
const courses = await Classes.find().exec();
if (courses)
for (const course of courses) {
await addCourseDescription(course);
}
return true;
} catch (err) {
console.log(`Error in adding descriptions: ${err}`);
}
}

/**
* Retrieves course description from Course API and adds course description field in Course database
*
* @param {string} semester: course roster semester for most recent offering of course
* @param {string} courseId: course ID of class stored in Course database
* @returns true if operation was successful, false otherwise
*/
export const addCourseDescription = async (course): Promise<boolean> => {
const courseId = course._id;
const semesters = course.classSems;
let semester;
const subject = course.classSub.toUpperCase();
const courseNum = course.classNum;
const courseFromDb = await Classes.findOne({ _id: courseId }).exec();
const checkDescription = courseFromDb.classDescription;

if (checkDescription && checkDescription !== null) {
console.log(`Already added description to ${subject} ${courseNum}`);
return true;
}

for (let i = semesters.length - 1; i >= 0; i--) {
semester = semesters[i];
try {
const result = await axios.get(
`https://classes.cornell.edu/api/2.0/search/classes.json?roster=${semester}&subject=${subject}`
);
const courses = result.data.data.classes;
for (const c of courses) {
if (c.catalogNbr === courseNum) {
const description = c.description && c.description !== null ? c.description : c.titleLong;
await Classes.updateOne(
{ _id: courseId },
{ $set: { classDescription: description } },
);
return true;
}
}
removeInvalidSem(course, semester);
} catch (err) {
console.log(`Semester ${semester} for course subject ${subject} not in Course API`);
removeInvalidSem(course, semester);
}
}
removeCourse(course);
console.log(`Error in adding description to course ${subject} ${courseNum}`);
return false;
}
19 changes: 18 additions & 1 deletion server/src/admin/admin.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
ReportReviewRequestType,
UpdateCourseMetrics,
VerifyAdminType,
VerifyManageAdminType
VerifyManageAdminType,
} from './admin.type';

import {
Expand All @@ -40,6 +40,7 @@ import {
addAllCrossList,
addCrossList,
addNewSemester,
addAllDescriptions,
} from '../../scripts';
import { fetchAddSubjects } from '../../scripts/populate-subjects';

Expand Down Expand Up @@ -402,3 +403,19 @@ export const addNewSemDb = async ({ auth, semester }: AdminAddSemesterType) => {
const result = await addCrossList(semester);
return result;
};

/**
* Adds all course descriptions to the database after updating the courses for the most recent semester.
*
* @param {Auth} auth: Object that represents the authentication of a request being passed in.
* @returns true if operation was successful, false if operations was not successful, null if token not admin
*/
export const addCourseDescriptionsDb = async ({ auth }: VerifyAdminType) => {
const userIsAdmin = verifyTokenAdmin({ auth });
if (!userIsAdmin) {
return null;
}

const descriptionResult = await addAllDescriptions();
return descriptionResult;
}
25 changes: 24 additions & 1 deletion server/src/admin/admin.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import {
getAdminUsers,
removeAdmin,
addAdmin,
approveReviews
approveReviews,
addCourseDescriptionsDb
} from './admin.controller';

export const adminRouter = express.Router();
Expand Down Expand Up @@ -444,6 +445,28 @@ adminRouter.post('/professors/reset', async (req, res) => {
}
});

adminRouter.post('/course/desc', async (req, res) => {
const { token }: AdminRequestType = req.body;
try {
const auth = new Auth({ token });
const result = await addCourseDescriptionsDb({ auth });
console.log(result)

if (result) {
res.status(200);
res.set('Connection', 'close');
res.json({ message: 'Course descriptions added!' });
return res;
}

return res
.status(400)
.json({ error: 'Course descriptions were unable to be added!' });
} catch (err) {
return res.status(500).json({ error: `Internal Server Error: ${err}` });
}
});

adminRouter.post('/subjects/update', async (req, res) => {
const { token }: AdminRequestType = req.body;
try {
Expand Down

0 comments on commit c9f07e3

Please sign in to comment.