Skip to content

Commit

Permalink
Merge pull request #178 from makeopensource/130-ability-to-make-cours…
Browse files Browse the repository at this point in the history
…e-public

130 ability to make course public
  • Loading branch information
jessehartloff authored Nov 5, 2024
2 parents 97a2f95 + 38853eb commit 1f33165
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 72 deletions.
2 changes: 1 addition & 1 deletion devU-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"prettier": "^2.3.0",
"rimraf": "^3.0.2",
"ts-jest": "^27.0.2",
"ts-node": "^10.0.0",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
"typescript": "^4.3.2"
},
Expand Down
29 changes: 18 additions & 11 deletions devU-api/scripts/populate-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,23 @@ async function SendPOST(path: string, requestBody: string | FormData, requesterE
return responseBody
}

async function CreateCourse(name: string, number: string, semester: string) {
async function CreateCourse(
name: string,
number: string,
semester: string,
isPublic: boolean
) {
const courseData = {
name: name,
semester: semester,
number: number,
startDate: '2024-01-24T00:00:00-0500',
endDate: '2024-05-10T23:59:59-0500',
}
console.log('Creating course: ', courseData.name)
return await SendPOST('/courses/instructor', JSON.stringify(courseData), 'admin')
name: name,
semester: semester,
number: number,
startDate: '2024-01-24T00:00:00-0500',
endDate: '2024-05-10T23:59:59-0500',
is_public: isPublic // Include the public property
};

console.log('Creating course: ', courseData.name);
return await SendPOST('/courses/instructor', JSON.stringify(courseData), 'admin');
}

async function joinCourse(courseId: number, userId: number, role: string) {
Expand Down Expand Up @@ -208,8 +215,8 @@ async function runCourseAndSubmission() {
const jones = await fetchToken('[email protected]', 'jones')

//Create courses
const courseId1 = (await CreateCourse('Testing Course Name1', 'CSE101', 's2024')).id
const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024')).id
const courseId1 = (await CreateCourse('Testing Course Name1', 'CSE101', 's2024',true)).id
const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024',true)).id

//Enroll students
await joinCourse(courseId1, billy, 'student')
Expand Down
7 changes: 7 additions & 0 deletions devU-api/src/entities/course/course.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,11 @@ export default class CourseModel {

@DeleteDateColumn({ name: 'deleted_at' })
deletedAt?: Date

@Column({ type: 'boolean', name: 'is_public', default: false })
isPublic: boolean;

@Column({ name: 'private_data', type: 'timestamp', default: () => 'now()' })
private_data?: Date;

}
2 changes: 2 additions & 0 deletions devU-api/src/entities/course/course.serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ export function serialize(course: CourseModel): Course {
endDate: course.endDate.toISOString(),
createdAt: course.createdAt.toISOString(),
updatedAt: course.updatedAt.toISOString(),
isPublic: course.isPublic,
private_data: course.private_data ? course.private_data.toISOString() : undefined
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export class addAssignmentsAndCourses1626719306608 implements MigrationInterface
"created_at" TIMESTAMP NOT NULL DEFAULT now(),
"updated_at" TIMESTAMP NOT NULL DEFAULT now(),
"deleted_at" TIMESTAMP,
"is_public" boolean NOT NULL DEFAULT false,
"private_data" TIMESTAMP NOT NULL DEFAULT now(),
CONSTRAINT "courses_primary_key_constraint" PRIMARY KEY ("id")
)`
)
Expand Down
10 changes: 10 additions & 0 deletions devU-client/src/components/listItems/courseListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,17 @@ const CourseListItem = ({course, isOpen}: Props) => {
{infoSection("Course Number", course.number)}
{infoSection("Semester", prettyPrintSemester(course.semester))}
{infoSection("Start/End Date", prettyPrintDate(course.startDate), prettyPrintDate(course.endDate))}
<div className={styles.courseVisibility}>
{course && (
course.isPublic ? (
<span className={styles.public}>Public Course</span>
) : (
<span className={styles.private}>Private Course</span>
)
)}
</div>
</Link>

}
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ const CourseUpdatePage = ({ }) => {
name: '',
number: '',
semester: '',
isPublic: false
})
const [startDate, setStartDate] = useState(new Date().toISOString())
const [endDate, setEndDate] = useState(new Date().toISOString())
const [studentEmail, setStudentEmail] = useState("")
const [emails, setEmails] = useState<string[]>([])
const [invalidFields, setInvalidFields] = useState(new Map<string, string>())
const [privateDate, setPrivateDate] = useState(new Date().toISOString().split("T")[0]);

const { courseId } = useParams() as UrlParams
useEffect(() => {
Expand All @@ -60,9 +62,11 @@ const CourseUpdatePage = ({ }) => {
name: res.name,
number: res.number,
semester: res.semester,
isPublic: res.isPublic
});
setStartDate(new Date(res.startDate).toISOString().split("T")[0]);
setEndDate(new Date(res.endDate).toISOString().split("T")[0]);
setPrivateDate(new Date(res.privateDate).toISOString().split("T")[0]);
isMounted = true;
});
}
Expand All @@ -80,17 +84,24 @@ const CourseUpdatePage = ({ }) => {
setFormData(prevState => ({ ...prevState, [key]: value }))
}
}

const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData(prevState => ({ ...prevState, isPublic: e.target.checked }));
};
const handleStartDateChange = (event: React.ChangeEvent<HTMLInputElement>) => { setStartDate(event.target.value) }
const handleEndDateChange = (event: React.ChangeEvent<HTMLInputElement>) => { setEndDate(event.target.value) }

const handlePrivateDateChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setPrivateDate(event.target.value);
};
const handleCourseUpdate = () => {
const finalFormData = {
name: formData.name,
number: formData.number,
semester: formData.semester,
startDate: startDate + "T16:02:41.849Z",
endDate: endDate + "T16:02:41.849Z",
isPublic: formData.isPublic,
privateDate: privateDate + "T16:02:41.849Z",
}

RequestService.put(`/api/courses/${courseId}`, finalFormData)
Expand Down Expand Up @@ -299,6 +310,20 @@ const CourseUpdatePage = ({ }) => {
<label htmlFor='end-date'>End Date *</label>
<input type="date" id="end-date" value={endDate} onChange={handleEndDateChange} />
</div>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'column', gap: '5px' }}>
<label htmlFor='private-date'>Private Date *</label>
<input type="date" id="private-date" value={privateDate} onChange={handlePrivateDateChange} />
</div>
<div>
<label>
<input
type="checkbox"
checked={formData.isPublic}
onChange={handleCheckboxChange}
/>
Make this course public
</label>
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<button className='btnPrimary' onClick={handleCourseUpdate}>Update Course</button>
Expand Down
147 changes: 88 additions & 59 deletions devU-client/src/components/pages/forms/courses/coursesFormPage.tsx
Original file line number Diff line number Diff line change
@@ -1,102 +1,131 @@
import React, { useState } from 'react'
import { useHistory } from 'react-router-dom'
import { ExpressValidationError } from 'devu-shared-modules'

import PageWrapper from 'components/shared/layouts/pageWrapper'

import RequestService from 'services/request.service'

import { useActionless } from 'redux/hooks'
import TextField from 'components/shared/inputs/textField'
import { SET_ALERT } from 'redux/types/active.types'
import formStyles from './coursesFormPage.scss'
import { applyMessageToErrorFields, removeClassFromField } from "../../../../utils/textField.utils";

import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';
import RequestService from 'services/request.service';
import { useActionless } from 'redux/hooks';
import TextField from 'components/shared/inputs/textField';
import { SET_ALERT } from 'redux/types/active.types';
import formStyles from './coursesFormPage.scss';
import PageWrapper from 'components/shared/layouts/pageWrapper';

const EditCourseFormPage = () => {
const [setAlert] = useActionless(SET_ALERT)
const [setAlert] = useActionless(SET_ALERT);
const history = useHistory();

const [formData, setFormData] = useState({
name: '',
number: '',
semester: '',
})
isPublic: false
});

const [startDate, setStartDate] = useState(new Date().toISOString().split("T")[0])
const [endDate, setEndDate] = useState(new Date().toISOString().split("T")[0])
const [invalidFields, setInvalidFields] = useState(new Map<string, string>())
const [startDate, setStartDate] = useState(new Date().toISOString().split("T")[0]);
const [endDate, setEndDate] = useState(new Date().toISOString().split("T")[0]);
const [privateDate, setPrivateDate] = useState(new Date().toISOString().split("T")[0]);

const handleChange = (value: String, e: React.ChangeEvent<HTMLInputElement>) => {
const key = e.target.id
setFormData(prevState => ({ ...prevState, [key]: value }))
const handleChange = (value: string, e: React.ChangeEvent<HTMLInputElement>) => {
const key = e.target.id;
setFormData(prevState => ({ ...prevState, [key]: value }));
};

const newInvalidFields = removeClassFromField(invalidFields, key)
setInvalidFields(newInvalidFields)
}
const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData(prevState => ({ ...prevState, isPublic: e.target.checked }));
};

const handleStartDateChange = (event: React.ChangeEvent<HTMLInputElement>) => { setStartDate(event.target.value) }
const handleEndDateChange = (event: React.ChangeEvent<HTMLInputElement>) => { setEndDate(event.target.value) }
const handleStartDateChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setStartDate(event.target.value);
};

const handleEndDateChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setEndDate(event.target.value);
};

const handlePrivateDateChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setPrivateDate(event.target.value);
};

const formatDateForSubmission = (date: string) => {
return new Date(date).toISOString();
};

const isFormValid = () => {
return formData.name && formData.number && formData.semester && startDate && endDate;
};

const handleSubmit = () => {
const finalFormData = {
name: formData.name,
number: formData.number,
semester: formData.semester,
startDate: startDate + "T16:02:41.849Z",
endDate: endDate + "T16:02:41.849Z",
}
startDate: formatDateForSubmission(startDate),
endDate: formatDateForSubmission(endDate),
isPublic: formData.isPublic,
privateDate: formatDateForSubmission(privateDate)
};

RequestService.post('/api/courses/instructor', finalFormData)
.then(() => {
setAlert({ autoDelete: true, type: 'success', message: 'Course Added' })
history.goBack()
setAlert({ autoDelete: true, type: 'success', message: 'Course Added' });
history.goBack();
})
.catch((err: ExpressValidationError[] | Error) => {
const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message

const newFields = new Map<string, string>()
Array.isArray(err) ? err.map((e) => applyMessageToErrorFields(newFields, e.param, e.msg)) : newFields
setInvalidFields(newFields);
setAlert({ autoDelete: false, type: 'error', message })
})
.finally(() => {
})
}
.catch((err) => {
setAlert({ autoDelete: false, type: 'error', message: err.message });
});
};

return (
<PageWrapper>
<h1>Create Course</h1>

<div className={formStyles.courseFormWrapper}>
<div className={formStyles.createDetailsForm}>
<TextField id='name' label={"Course Name*"} onChange={handleChange} value={formData.name}
invalidated={!!invalidFields.get("name")} helpText={invalidFields.get("name")} />
<TextField id='number' label={"Course Number*"} onChange={handleChange} value={formData.number}
invalidated={!!invalidFields.get("number")} helpText={invalidFields.get("number")} />
<TextField id='semester' label={"Semester*"} onChange={handleChange} value={formData.semester}
placeholder='Ex. f2022, w2023, s2024' invalidated={!!invalidFields.get("semester")}
helpText={invalidFields.get("semester")} />
<TextField
id='name'
label={"Course Name*"}
onChange={handleChange}
value={formData.name}
/>
<TextField
id='number'
label={"Course Number*"}
onChange={handleChange}
value={formData.number}
/>
<TextField
id='semester'
label={"Semester*"}
onChange={handleChange}
value={formData.semester}
/>
<div className={formStyles.datepickerContainer}>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'column', gap: '5px' }}>
<div>
<label htmlFor='start-date'>Start Date *</label>
<input type="date" id="start-date" value={startDate} onChange={handleStartDateChange} />
</div>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'column', gap: '5px' }}>
<div>
<label htmlFor='end-date'>End Date *</label>
<input type="date" id="end-date" value={endDate} onChange={handleEndDateChange} />
</div>
<div>
<label htmlFor='private-date'>Private Date *</label>
<input type="date" id="private-date" value={privateDate} onChange={handlePrivateDateChange} />
</div>
</div>
<div>
<label>
<input
type="checkbox"
checked={formData.isPublic}
onChange={handleCheckboxChange}
/>
Make this course public
</label>
</div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<button className='btnPrimary' onClick={handleSubmit}>Create Course</button>
<button className='btnPrimary' onClick={handleSubmit} disabled={!isFormValid()}>Create Course</button>
</div>
</div>
</div>
</PageWrapper>
)

}

);
};

export default EditCourseFormPage
export default EditCourseFormPage;
4 changes: 4 additions & 0 deletions devU-shared/src/types/course.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ export type Course = {
endDate: string
createdAt?: string
updatedAt?: string
isPublic?: boolean;
private_data?: string;
allowlist?: string[];
blocklist?: string[];
}

0 comments on commit 1f33165

Please sign in to comment.