Skip to content

Commit ac55fa6

Browse files
authored
Merge pull request #106 from ChangePlusPlusVandy/anna/zoom
Anna/zoom
2 parents 7774729 + 82e1814 commit ac55fa6

File tree

23 files changed

+705
-296
lines changed

23 files changed

+705
-296
lines changed

backend/app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import emailRoutes from "./routes/emailRoutes";
2323
import speakerRoutes from "./routes/speakerRoutes";
2424
import pdfRoutes from "./routes/pdfRoutes";
2525
import emailTemplateRoutes from "./routes/emailTemplateRoutes";
26+
import zoomRoutes from "./routes/zoomRoutes";
2627

2728
// Import middleware
2829
import { notFound, errorHandler } from "./middlewares/errorMiddleware";
@@ -93,6 +94,7 @@ app.use("/api/emails", verifyFirebaseAuth, emailRoutes);
9394
app.use("/api/emailTemplates", verifyFirebaseAuth, emailTemplateRoutes);
9495
app.use("/api/speakers", verifyFirebaseAuth, speakerRoutes);
9596
app.use("/api/upload", verifyFirebaseAuth, uploadRoutes);
97+
app.use("/api/zoom", verifyFirebaseAuth, zoomRoutes)
9698
app.use("/api/certificatePDFs", verifyFirebaseAuth, pdfRoutes);
9799

98100
app.use(notFound);

backend/controllers/courseController.ts

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,9 @@ export const getCourses = async (
2424
): Promise<void> => {
2525
try {
2626
const filters = req.query;
27-
28-
// Populate ratings and components fields as needed
2927
const courseResponses = await Course.find(filters)
30-
.populate(["ratings", "components"])
28+
.populate(["ratings"])
3129
.exec();
32-
3330
res.status(200).json({
3431
success: true,
3532
count: courseResponses.length,
@@ -56,7 +53,7 @@ export const getCourseById = async (
5653
if (id) {
5754
// Find course by ID and populate related fields
5855
const course = await Course.findById(id)
59-
.populate(["ratings", "components", "managers"])
56+
.populate(["ratings", "managers"])
6057
.exec();
6158

6259
if (!course) {
@@ -105,8 +102,6 @@ export const createCourse = async (
105102
ratings,
106103
className,
107104
discussion,
108-
components,
109-
isLive,
110105
categories,
111106
creditNumber,
112107
courseDescription,
@@ -115,17 +110,14 @@ export const createCourse = async (
115110
cost,
116111
instructorDescription,
117112
instructorRole,
118-
lengthCourse,
119-
time,
120113
instructorName,
121-
isInPerson,
122114
students,
123115
managers,
124116
speakers,
125-
courseType,
126117
regStart,
127118
regEnd,
128119
productType,
120+
productInfo,
129121
shortUrl,
130122
draft,
131123
} = req.body;
@@ -134,23 +126,18 @@ export const createCourse = async (
134126
if (!draft)
135127
if (
136128
!className ||
137-
isLive === undefined ||
138129
creditNumber === undefined ||
139130
!courseDescription ||
140131
!thumbnailPath ||
141132
cost === undefined ||
142-
!lengthCourse ||
143-
!time ||
144133
!instructorName ||
145-
isInPerson === undefined ||
146-
!courseType ||
147134
!regEnd
148135
) {
149136
// console.log("[createCourse] Validation failed. Missing required fields");
150137
res.status(400).json({
151138
success: false,
152139
message:
153-
"Please provide className, isLive, creditNumber, thumbnailPath, cost, lengthCourse, time, instructorName, isInPerson, courseType, and regStart",
140+
"Please provide className, isLive, creditNumber, thumbnailPath, cost, lengthCourse, instructorName, and regStart",
154141
});
155142
return;
156143
}
@@ -172,8 +159,6 @@ export const createCourse = async (
172159
ratings,
173160
className,
174161
discussion,
175-
components,
176-
isLive,
177162
categories,
178163
creditNumber,
179164
courseDescription,
@@ -182,17 +167,14 @@ export const createCourse = async (
182167
cost,
183168
instructorDescription,
184169
instructorRole,
185-
lengthCourse,
186-
time,
187170
instructorName,
188-
isInPerson,
189171
students,
190172
managers,
191173
speakers,
192-
courseType,
193174
regStart,
194175
regEnd,
195176
productType,
177+
productInfo,
196178
shortUrl,
197179
draft,
198180
});
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import {Request, Response} from "express";
2+
import dotenv from "dotenv";
3+
dotenv.config();
4+
// @desc Get a bearer token
5+
// @route GET /api/zoom/token
6+
// @access Public
7+
async function getToken(){
8+
try {
9+
const credentials = btoa(`${process.env.ZOOM_CLIENT_ID}:${process.env.ZOOM_CLIENT_SECRET}`);
10+
11+
const token = await fetch(`https://zoom.us/oauth/token?grant_type=account_credentials&account_id=${process.env.ZOOM_ACCOUNT_ID}`, {
12+
method: "POST",
13+
headers: {
14+
"Authorization": `Basic ${credentials}`,
15+
"Content-Type": "application/x-www-form-urlencoded"
16+
}
17+
})
18+
return (await token.json()).access_token
19+
} catch (error) {
20+
console.error(error);
21+
return undefined
22+
}
23+
}
24+
25+
export const getMeetings = async (
26+
req: Request,
27+
res: Response
28+
): Promise<void> => {
29+
try {
30+
await getToken().then(async (token) => {
31+
const response = await fetch(`https://api.zoom.us/v2/users/${process.env.ZOOM_USER_ID}/meetings`, {
32+
method: "GET",
33+
headers: {
34+
"Authorization": `Bearer ${token}`,
35+
"Content-Type": "application/x-www-form-urlencoded",
36+
}
37+
})
38+
let meetings = await response.json()
39+
res.status(200).json(meetings)
40+
})
41+
} catch (error) {
42+
console.error(error);
43+
res.status(500).json({
44+
success: false,
45+
message: "Internal service error",
46+
});
47+
}
48+
};
49+
50+
export const getWebinars = async (
51+
req: Request,
52+
res: Response
53+
): Promise<void> => {
54+
try {
55+
await getToken().then(async (token) => {
56+
57+
const response = await fetch(`https://api.zoom.us/v2/users/${process.env.ZOOM_USER_ID}/webinars`, {
58+
method: "GET",
59+
headers: {
60+
"Authorization": `Bearer ${token}`,
61+
"Content-Type": "application/x-www-form-urlencoded",
62+
}
63+
})
64+
let webinars = (await response.json()).webinars
65+
res.status(200).json({
66+
webinars: webinars
67+
})
68+
})
69+
} catch (error) {
70+
console.error(error);
71+
res.status(500).json({
72+
success: false,
73+
message: "Internal service error",
74+
});
75+
}
76+
};
77+
78+
export const createMeeting = async (
79+
req: Request,
80+
res: Response
81+
): Promise<void> => {
82+
const { topic, startTime, duration } = req.body;
83+
try {
84+
await getToken().then(async (token) => {
85+
86+
const response = await fetch(`https://api.zoom.us/v2/users/${process.env.ZOOM_USER_ID}/meetings`, {
87+
method: 'POST',
88+
headers: {
89+
'Content-Type': 'application/json',
90+
"Authorization": `Bearer ${token}`,
91+
},
92+
body: JSON.stringify({
93+
topic,
94+
start_time: startTime,
95+
duration: duration
96+
})
97+
});
98+
let meeting = await response.json()
99+
console.log(meeting)
100+
res.status(200).json({
101+
meeting: meeting
102+
})
103+
})
104+
} catch (error) {
105+
console.error(error);
106+
res.status(500).json({
107+
success: false,
108+
message: "Internal service error",
109+
});
110+
}
111+
};
112+
113+
export const createWebinar = async (
114+
req: Request,
115+
res: Response
116+
): Promise<void> => {
117+
const { topic, startTime, duration } = req.body;
118+
try {
119+
await getToken().then(async (token) => {
120+
const response = await fetch(`https://api.zoom.us/v2/users/${process.env.ZOOM_USER_ID}/webinars`, {
121+
method: 'POST',
122+
headers: {
123+
'Content-Type': 'application/json',
124+
"Authorization": `Bearer ${token}`,
125+
},
126+
body: JSON.stringify({
127+
topic,
128+
start_time: startTime,
129+
duration: duration
130+
})
131+
});
132+
let webinar = await response.json()
133+
console.log(webinar)
134+
res.status(200).json({
135+
webinar: webinar
136+
})
137+
})
138+
} catch (error) {
139+
console.error(error);
140+
res.status(500).json({
141+
success: false,
142+
message: "Internal service error",
143+
});
144+
}
145+
};

backend/models/courseModel.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export interface ICourse extends Document {
99
ratings: (mongoose.Types.ObjectId | IRating)[];
1010
className: string;
1111
discussion: string;
12-
components: (mongoose.Types.ObjectId | Object)[];
1312
isLive: boolean;
1413
categories: string[];
1514
creditNumber: number;
@@ -20,16 +19,14 @@ export interface ICourse extends Document {
2019
instructorName: string;
2120
instructorDescription: string;
2221
instructorRole: string;
23-
courseType: "webinar" | "course" | "meeting";
24-
lengthCourse: number;
25-
time: Date;
26-
isInPerson: boolean;
2722
students: (mongoose.Types.ObjectId | IUser)[]; //for the users
2823
managers: (mongoose.Types.ObjectId | IUser)[];
2924
speakers: (mongoose.Types.ObjectId | ISpeaker)[];
3025
regStart: Date;
3126
regEnd: Date;
32-
productType: string[];
27+
//Virtual Training - Live Meeting, In-Person Training, Virtual Training - On Demand, Virtual Training - Live Webinar
28+
productType: string;
29+
productInfo: string
3330
shortUrl: string;
3431
draft: boolean;
3532
}
@@ -50,8 +47,6 @@ const CourseSchema: Schema = new Schema(
5047
],
5148
className: { type: String, required: true },
5249
discussion: { type: String, required: false },
53-
components: [{ type: Schema.Types.Mixed, required: false }],
54-
isLive: { type: Boolean, required: false },
5550
categories: [{ type: String, required: false }],
5651
creditNumber: { type: Number, required: false },
5752
courseDescription: { type: String, required: false },
@@ -60,7 +55,6 @@ const CourseSchema: Schema = new Schema(
6055
cost: { type: Number, required: false },
6156
instructorDescription: { type: String, required: false },
6257
instructorRole: { type: String, required: false },
63-
lengthCourse: { type: Number, required: false },
6458
time: { type: Date, required: false },
6559
instructorName: { type: String, required: false },
6660
isInPerson: { type: Boolean, required: false },
@@ -70,11 +64,6 @@ const CourseSchema: Schema = new Schema(
7064
ref: "User",
7165
},
7266
],
73-
courseType: {
74-
type: String,
75-
enum: ["webinar", "course", "meeting"],
76-
required: true,
77-
},
7867
managers: [
7968
{
8069
type: Schema.Types.ObjectId,
@@ -89,7 +78,8 @@ const CourseSchema: Schema = new Schema(
8978
],
9079
regStart: { type: Date, required: false },
9180
regEnd: { type: Date, required: false },
92-
productType: [{ type: String, required: false }],
81+
productType: { type: String, required: false },
82+
productInfo: {type:String, required: false},
9383
shortUrl: { type: String, required: false },
9484
draft: { type: Boolean, required: true, default: true },
9585
},

backend/routes/zoomRoutes.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import express from "express";
2+
import {
3+
createMeeting, createWebinar,
4+
getMeetings, getWebinars
5+
} from "../controllers/zoomController";
6+
7+
const router = express.Router();
8+
9+
router.get("/meetings", getMeetings);
10+
router.get("/webinars", getWebinars);
11+
12+
router.post("/meeting", createMeeting)
13+
router.post("/webinar", createWebinar)
14+
15+
export default router;

frontend/src/components/AdminCoursePreview/AdminCoursePreview.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,6 @@ function AdminCoursePreview({
7171
return null;
7272
}
7373

74-
if (isLive !== null && product.course.isLive !== isLive) {
75-
return null;
76-
}
77-
7874
if (
7975
category !== null &&
8076
(!product.course.categories ||
@@ -138,7 +134,6 @@ function AdminCoursePreview({
138134

139135
<Calendar className="w-12" />
140136
<span className="text-gray-500 w-24">
141-
{product.course.isLive ? "Live" : "Virtual"} Event{" "}
142137
{product.startTime.getMonth() + 1}/{product.startTime.getDate()}/
143138
{product.startTime.getFullYear()} at {product.startTime.getHours()}:
144139
{product.startTime.getMinutes().toString().padStart(2, "0")}{" "}

frontend/src/pages/Admin/AdminPage.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,6 @@ export default function AdminPage() {
7777
status: "Ongoing",
7878
avgRating: calculateAverageRating(course.ratings),
7979
startTime: new Date(course.time),
80-
endTime: new Date(
81-
new Date(course.time).getTime() + course.lengthCourse * 60000
82-
),
8380
timeZone: "(CST)",
8481
selected: false,
8582
})

frontend/src/pages/Admin/ProductPage/ProductPage.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,9 +256,6 @@ export default function ProductPage() {
256256
status: "Ongoing",
257257
avgRating: calculateAverageRating(course.ratings),
258258
startTime: new Date(course.time),
259-
endTime: new Date(
260-
new Date(course.time).getTime() + course.lengthCourse * 60000
261-
),
262259
timeZone: "(CST)",
263260
selected: false,
264261
categories: categories,

0 commit comments

Comments
 (0)