def unschedule(): params = request.get_json() term_id = params.get('termId') section_id = params.get('sectionId') course = SisSection.get_course(term_id, section_id, include_deleted=True) if (term_id and section_id) else None if not course: raise BadRequestError('Required params missing or invalid') if not (course['scheduled'] or course['hasNecessaryApprovals']): raise BadRequestError(f'Section id {section_id}, term id {term_id} is not currently scheduled or queued for scheduling') Approval.delete(term_id=term_id, section_id=section_id) Scheduled.delete(term_id=term_id, section_id=section_id) event_id = (course.get('scheduled') or {}).get('kalturaScheduleId') if event_id: try: Kaltura().delete(event_id) except (KalturaClientException, KalturaException) as e: message = f'Failed to delete Kaltura schedule: {event_id}' app.logger.error(message) app.logger.exception(e) send_system_error_email( message=f'{message}\n\n<pre>{traceback.format_exc()}</pre>', subject=message, ) CoursePreference.update_opt_out( term_id=term_id, section_id=section_id, opt_out=True, ) return tolerant_jsonify(SisSection.get_course(term_id, section_id, include_deleted=True))
def queue_emails(): params = request.get_json() term_id = params.get('termId') section_ids = params.get('sectionIds') template_type = params.get('emailTemplateType') if term_id and section_ids and template_type: section_ids_already_queued = QueuedEmail.get_all_section_ids( template_type=template_type, term_id=term_id) section_ids_to_queue = [ id_ for id_ in section_ids if id_ not in section_ids_already_queued ] for section_id in section_ids_to_queue: QueuedEmail.create(section_id=section_id, template_type=template_type, term_id=term_id) section_id_count = len(section_ids) queued_count = len(section_ids_to_queue) if queued_count < section_id_count: message = f""" {len(section_ids_already_queued)} '{template_type}' emails were already queued up for the {'course' if section_id_count == 1 else 'courses'} you submitted. Thus, {'no' if queued_count == 0 else f'only {queued_count}'} emails added to the queue. """ else: message = f'{queued_count} \'{template_type}\' emails will be sent.' return tolerant_jsonify({ 'message': message, }) else: raise BadRequestError('Required parameters are missing.')
def ping(): b_connected_ping = None canvas_ping = None db_ping = None kaltura_ping = None status = 200 try: b_connected_ping = BConnected().ping() canvas_ping = _ping_canvas() db_ping = _db_status() kaltura_ping = Kaltura().ping() except Exception as e: status = 500 subject = str(e) subject = f'{subject[:50]}...' if len(subject) > 50 else subject message = f'Error during /api/ping: {subject}' app.logger.error(message) app.logger.exception(e) if app.config['EMAIL_IF_PING_HAS_ERROR']: send_system_error_email( message=f'{message}\n\n<pre>{traceback.format_exc()}</pre>', subject=message, ) finally: return tolerant_jsonify( { 'app': True, 'bConnected': b_connected_ping, 'canvas': canvas_ping, 'db': db_ping, 'kaltura': kaltura_ping, }, status=status, )
def test_email_template(template_id): email_template = EmailTemplate.get_template(template_id) if email_template: course = SisSection.get_course(term_id=app.config['CURRENT_TERM_ID'], section_id='12597') template = EmailTemplate.get_template(template_id) subject_line = interpolate_email_content( course=course, recipient_name=current_user.name, templated_string=template.subject_line, ) message = interpolate_email_content( course=course, recipient_name=current_user.name, templated_string=template.message, ) BConnected().send( recipients=[ { 'email': current_user.email_address, 'name': current_user.name, 'uid': current_user.uid, }, ], message=message, subject_line=subject_line, ) return tolerant_jsonify( {'message': f'Email sent to {current_user.email_address}'}), 200 else: raise ResourceNotFoundError('No such email_template')
def app_config(): term_id = app.config['CURRENT_TERM_ID'] return tolerant_jsonify({ 'canvasBaseUrl': app.config['CANVAS_BASE_URL'], 'courseCaptureExplainedUrl': app.config['COURSE_CAPTURE_EXPLAINED_URL'], 'courseCapturePoliciesUrl': app.config['COURSE_CAPTURE_POLICIES_URL'], 'currentTermId': term_id, 'currentTermName': term_name_for_sis_id(term_id), 'devAuthEnabled': app.config['DEVELOPER_AUTH_ENABLED'], 'diabloEnv': app.config['DIABLO_ENV'], 'ebEnvironment': app.config['EB_ENVIRONMENT'] if 'EB_ENVIRONMENT' in app.config else None, 'emailTemplateTypes': EmailTemplate.get_template_type_options(), 'publishTypeOptions': NAMES_PER_PUBLISH_TYPE, 'roomCapabilityOptions': Room.get_room_capability_options(), 'searchFilterOptions': get_search_filter_options(), 'searchItemsPerPage': app.config['SEARCH_ITEMS_PER_PAGE'], 'supportEmailAddress': app.config['EMAIL_DIABLO_SUPPORT'], 'timezone': app.config['TIMEZONE'], })
def test_email_template(template_id): email_template = EmailTemplate.get_template(template_id) if email_template: course = SisSection.get_random_co_taught_course(app.config['CURRENT_TERM_ID']) template = EmailTemplate.get_template(template_id) publish_types = get_all_publish_types() recording_types = get_all_recording_types() def _get_interpolated_content(templated_string): return interpolate_content( course=course, pending_instructors=course['instructors'], previous_publish_type_name=NAMES_PER_PUBLISH_TYPE[publish_types[0]], previous_recording_type_name=NAMES_PER_RECORDING_TYPE[recording_types[0]], publish_type_name=NAMES_PER_PUBLISH_TYPE[publish_types[1]], recording_type_name=NAMES_PER_RECORDING_TYPE[recording_types[1]], recipient_name=current_user.name, templated_string=templated_string, ) BConnected().send( recipient={ 'email': current_user.email_address, 'name': current_user.name, 'uid': current_user.uid, }, message=_get_interpolated_content(template.message), subject_line=_get_interpolated_content(template.subject_line), ) return tolerant_jsonify({'message': f'Email sent to {current_user.email_address}'}), 200 else: raise ResourceNotFoundError('No such email_template')
def get_course(term_id, section_id): course = SisSection.get_course(term_id, section_id) if not course: raise ResourceNotFoundError(f'No section for term_id = {term_id} and section_id = {section_id}') if not current_user.is_admin and current_user.uid not in [i['uid'] for i in course['instructors']]: raise ForbiddenRequestError(f'Sorry, you are unauthorized to view the course {course["label"]}.') return tolerant_jsonify(course)
def available_jobs(): jobs = [] for job in _available_jobs(): jobs.append({ 'key': job['key'], 'description': job['description'], }) return tolerant_jsonify(jobs)
def logout(): logout_user() redirect_url = app.config['VUE_LOCALHOST_BASE_URL'] or request.url_root cas_logout_url = _cas_client().get_logout_url(redirect_url=redirect_url) return tolerant_jsonify({ 'casLogoutUrl': cas_logout_url, **current_user.to_api_json(), })
def approve(): term_id = app.config['CURRENT_TERM_ID'] term_name = term_name_for_sis_id(term_id) params = request.get_json() publish_type = params.get('publishType') recording_type = params.get('recordingType') section_id = params.get('sectionId') course = SisSection.get_course(term_id, section_id) if section_id else None if not course or publish_type not in get_all_publish_types() or recording_type not in get_all_recording_types(): raise BadRequestError('One or more required params are missing or invalid') if not current_user.is_admin and current_user.uid not in [i['uid'] for i in course['instructors']]: raise ForbiddenRequestError('Sorry, request unauthorized') if Approval.get_approval(approved_by_uid=current_user.uid, section_id=section_id, term_id=term_id): raise ForbiddenRequestError(f'You have already approved recording of {course["courseName"]}, {term_name}') meetings = course.get('meetings', {}).get('eligible', []) if len(meetings) != 1: raise BadRequestError('Unique eligible meeting pattern not found for course') meeting = meetings[0] location = meeting and meeting.get('location') room = Room.find_room(location=location) if not room: raise BadRequestError(f'{location} is not eligible for Course Capture.') previous_approvals = Approval.get_approvals_per_section_ids(section_ids=[section_id], term_id=term_id) approval = Approval.create( approved_by_uid=current_user.uid, approver_type_='admin' if current_user.is_admin else 'instructor', course_display_name=course['label'], publish_type_=publish_type, recording_type_=recording_type, room_id=room.id, section_id=section_id, term_id=term_id, ) if previous_approvals: # Compare the current approval with preferences submitted in previous approval previous_approval = previous_approvals[-1] if (approval.publish_type, approval.recording_type) != (previous_approval.publish_type, previous_approval.recording_type): notify_instructors_of_changes(course, approval, previous_approvals) all_approvals = previous_approvals + [approval] if len(course['instructors']) > len(all_approvals): approval_uids = [a.approved_by_uid for a in all_approvals] pending_instructors = [i for i in course['instructors'] if i['uid'] not in approval_uids] last_approver = next((i for i in course['instructors'] if i['uid'] == approval.approved_by_uid), None) if last_approver: notify_instructor_waiting_for_approval(course, last_approver, pending_instructors) return tolerant_jsonify(_after_approval(course=SisSection.get_course(term_id, section_id)))
def update_room_capability(): params = request.get_json() room_id = params.get('roomId') room = Room.get_room(room_id) if room_id else None if not room or 'capability' not in params: raise BadRequestError('Missing required parameters') capability = params.get('capability') room = Room.update_capability(room_id, capability) return tolerant_jsonify(room.to_api_json())
def admin_users(): api_json = [] admin_uids = [admin_user.uid for admin_user in AdminUser.all_admin_users()] for admin_user in get_calnet_users_for_uids(app=app, uids=admin_uids).values(): api_json.append({ 'email': admin_user.get('email'), 'name': admin_user.get('name'), 'uid': admin_user['uid'], }) return tolerant_jsonify(api_json)
def dev_auth_login(): if app.config['DEV_AUTH_ENABLED']: params = request.get_json() or {} uid = params.get('uid') password = params.get('password') if password != app.config['DEV_AUTH_PASSWORD']: return tolerant_jsonify({'message': 'Invalid credentials'}, 401) user = User(uid) if not user.is_active: msg = f'UID {uid} is neither an Admin user nor active in CalNet.' app.logger.error(msg) return tolerant_jsonify({'message': msg}, 403) if not login_user(user, force=True, remember=True): msg = f'The system failed to log in user with UID {uid}.' app.logger.error(msg) return tolerant_jsonify({'message': msg}, 403) return tolerant_jsonify(current_user.to_api_json(include_courses=True)) else: raise ResourceNotFoundError('Unknown path')
def start_job(job_key): job_class = next((job for job in BackgroundJobManager.available_job_classes() if job.key() == job_key), None) if job_class: app.logger.info( f'Current user ({current_user.uid}) started job {job_class.key()}') job_class(app.app_context).run(force_run=True) return tolerant_jsonify(_job_class_to_json(job_class)) else: raise ResourceNotFoundError(f'Invalid job_key: {job_key}')
def start_job(job_key): job = next((job for job in _available_jobs() if job['key'] == job_key), None) if job: job['class'](app.app_context).run_with_app_context() return tolerant_jsonify({ 'key': job['key'], 'description': job['description'], }) else: raise ResourceNotFoundError(f'Invalid job_key: {job_key}')
def update_opt_out(): params = request.get_json() term_id = params.get('termId') section_id = params.get('sectionId') opt_out = params.get('optOut') preferences = CoursePreference.update_opt_out( term_id=term_id, section_id=section_id, opt_out=opt_out, ) return tolerant_jsonify(preferences.to_api_json())
def get_room(room_id): room = Room.get_room(room_id) if room: api_json = room.to_api_json() api_json['courses'] = SisSection.get_courses_per_location( term_id=app.config['CURRENT_TERM_ID'], location=room.location, ) return tolerant_jsonify(api_json) else: raise ResourceNotFoundError('No such room')
def get_user(uid): user = get_calnet_user_for_uid(app=app, uid=uid) if user.get('isExpiredPerLdap', True): raise ResourceNotFoundError('No such user') else: courses = SisSection.get_courses_per_instructor_uid( term_id=app.config['CURRENT_TERM_ID'], instructor_uid=uid, ) user['courses'] = courses return tolerant_jsonify(user)
def app_version(): v = { 'version': version, } build_stats = load_json('config/build-summary.json') if build_stats: v.update(build_stats) else: v.update({ 'build': None, }) return tolerant_jsonify(v)
def get_course(term_id, section_id): course = SisSection.get_course(term_id, section_id, include_deleted=True) if not course: raise ResourceNotFoundError(f'No section for term_id = {term_id} and section_id = {section_id}') if not current_user.is_admin and current_user.uid not in [i['uid'] for i in course['instructors']]: raise ForbiddenRequestError(f'Sorry, you are unauthorized to view the course {course["label"]}.') if current_user.is_admin and course['scheduled']: # When debugging, the raw Kaltura-provided JSON is useful. event_id = course['scheduled'].get('kalturaScheduleId') course['scheduled']['kalturaSchedule'] = Kaltura().get_event(event_id) return tolerant_jsonify(course)
def job_disable(): params = request.get_json() job_id = params.get('jobId') disable = params.get('disable') if not job_id or disable is None: raise BadRequestError('Required parameters are missing.') job = Job.update_disabled(job_id=job_id, disable=disable) background_job_manager.restart() return tolerant_jsonify(job.to_api_json())
def auditorium(): params = request.get_json() room_id = params.get('roomId') room = Room.get_room(room_id) if room_id else None if room: is_auditorium = params.get('isAuditorium') if not room_id or is_auditorium is None: raise BadRequestError("'roomId' and 'isAuditorium' are required.") room = Room.set_auditorium(room_id, is_auditorium) return tolerant_jsonify(room.to_api_json()) else: raise ResourceNotFoundError('No such room')
def job_history(day_count): def _raise_error(): raise BadRequestError(f'Invalid day_count: {day_count}') try: days = int(day_count) if days < 1: _raise_error() return tolerant_jsonify([ h.to_api_json() for h in JobHistory.get_job_history_in_past_days(day_count=days) ]) except ValueError: _raise_error()
def queue_emails(): params = request.get_json() term_id = params.get('termId') section_id = params.get('sectionId') template_type = params.get('emailTemplateType') if not (term_id and section_id and template_type): raise BadRequestError('Required parameters are missing.') course = SisSection.get_course(term_id=term_id, section_id=section_id) for instructor in course['instructors']: if not QueuedEmail.create(section_id=section_id, recipient=instructor, template_type=template_type, term_id=term_id): raise BadRequestError(f"Failed to queue email of type '{template_type}'.") return tolerant_jsonify({ 'message': f"An email of type '{template_type}' has been queued.", })
def create_blackout(): params = request.get_json() name = params.get('name') start_date = params.get('startDate') end_date = params.get('endDate') if None in [name, start_date, end_date]: raise BadRequestError('Required parameters are missing.') start_date = _local_blackout_date_to_utc(f'{start_date}T00:00:00') end_date = _local_blackout_date_to_utc(f'{end_date}T23:59:59') _validate_date_range(start_date, end_date) blackout = Blackout.create(name=name, start_date=start_date, end_date=end_date) return tolerant_jsonify(blackout.to_api_json())
def app_config(): def _to_api_key(key): chunks = key.split('_') return f"{chunks[0].lower()}{''.join(chunk.title() for chunk in chunks[1:])}" return tolerant_jsonify( { **dict((_to_api_key(key), app.config[key]) for key in PUBLIC_CONFIGS), **{ 'currentTermName': term_name_for_sis_id(app.config['CURRENT_TERM_ID']), 'ebEnvironment': get_eb_environment(), 'emailTemplateTypes': EmailTemplate.get_template_type_options(), 'publishTypeOptions': NAMES_PER_PUBLISH_TYPE, 'roomCapabilityOptions': Room.get_room_capability_options(), 'searchFilterOptions': get_search_filter_options(), }, }, )
def create(): params = request.get_json() template_type = params.get('templateType') name = params.get('name') subject_line = params.get('subjectLine') message = params.get('message') if None in [template_type, name, subject_line, message]: raise BadRequestError('Required parameters are missing.') email_template = EmailTemplate.create( template_type=template_type, name=name, subject_line=subject_line, message=message, ) return tolerant_jsonify(email_template.to_api_json())
def update_opt_out(): params = request.get_json() term_id = params.get('termId') section_id = params.get('sectionId') course = SisSection.get_course(term_id, section_id) if (term_id and section_id) else None opt_out = params.get('optOut') if not course or opt_out is None: raise BadRequestError('Required params missing or invalid') if course['scheduled']: raise BadRequestError('Cannot update opt-out on scheduled course') preferences = CoursePreference.update_opt_out( term_id=term_id, section_id=section_id, opt_out=opt_out, ) return tolerant_jsonify(preferences.to_api_json())
def job_schedule(): api_json = { 'autoStart': app.config['JOBS_AUTO_START'], 'jobs': [], 'secondsBetweenJobsCheck': app.config['JOBS_SECONDS_BETWEEN_PENDING_CHECK'], 'startedAt': to_isoformat(background_job_manager.get_started_at()), } for job in Job.get_all(include_disabled=True): job_class = next( (j for j in BackgroundJobManager.available_job_classes() if j.key() == job.key), None) if job_class: api_json['jobs'].append({ **job.to_api_json(), **_job_class_to_json(job_class), }) return tolerant_jsonify(api_json)
def update_schedule(): params = request.get_json() job_id = params.get('jobId') schedule_type = params.get('type') schedule_value = params.get('value') if not job_id or not schedule_type or not schedule_value: raise BadRequestError('Required parameters are missing.') job = Job.get_job(job_id=job_id) if not job.disabled or JobHistory.is_job_running(job_key=job.key): raise BadRequestError( 'You cannot edit job schedule if job is either enabled or running.' ) job = Job.update_schedule(job_id=job_id, schedule_type=schedule_type, schedule_value=schedule_value) background_job_manager.restart() return tolerant_jsonify(job.to_api_json())