ilios/frontend

View on GitHub
packages/ilios-common/addon/services/current-user.js

Summary

Maintainability
B
5 hrs
Test Coverage
import { isEmpty } from '@ember/utils';
import { get } from '@ember/object';
import Service, { service } from '@ember/service';
import { DateTime } from 'luxon';
import jwtDecode from 'ilios-common/utils/jwt-decode';
import { uniqueValues } from 'ilios-common/utils/array-helpers';

export default class CurrentUserService extends Service {
  @service store;
  @service session;
  _userPromise = null;

  get currentUserId() {
    if (
      !this.session ||
      !this.session.data ||
      !this.session.data.authenticated ||
      !this.session.data.authenticated.jwt
    ) {
      return null;
    }
    const obj = jwtDecode(this.session.data.authenticated.jwt);

    return get(obj, 'user_id');
  }

  async getModel() {
    const currentUserId = this.currentUserId;
    if (!currentUserId) {
      return null;
    }
    const user = this.store.peekRecord('user', currentUserId);
    if (user) {
      return user;
    }

    if (!this._userPromise) {
      this._userPromise = this.store.findRecord('user', currentUserId, {
        include: 'sessionMaterialStatuses',
      });
    }
    return await this._userPromise;
  }

  async getUserRoleTitles() {
    const user = await this.getModel();
    if (!user) {
      return [];
    }
    const roles = await user.get('roles');
    return roles.map((role) => role.get('title').toLowerCase());
  }

  async getIsStudent() {
    const roleTitles = await this.getUserRoleTitles();
    return roleTitles.includes('student');
  }

  async isFormerStudent() {
    const roleTitles = await this.getUserRoleTitles();
    return roleTitles.includes('former student');
  }

  async getActiveRelatedCoursesInThisYearAndLastYear() {
    const user = await this.getModel();
    if (isEmpty(user)) {
      return [];
    }
    let currentYear = DateTime.now().year;
    const currentMonth = DateTime.now().month;
    if (currentMonth < 6) {
      currentYear--;
    }
    const previousYear = currentYear - 1;
    const nextYear = currentYear + 1;
    return await this.store.query('course', {
      my: true,
      filters: {
        year: [previousYear, currentYear, nextYear],
        locked: false,
        archived: false,
      },
    });
  }

  getBooleanAttributeFromToken(attribute) {
    const session = this.session;
    if (isEmpty(session)) {
      return false;
    }

    const jwt = session.get('data.authenticated.jwt');

    if (isEmpty(jwt)) {
      return false;
    }
    const obj = jwtDecode(jwt);

    return !!get(obj, attribute);
  }
  get isRoot() {
    return this.getBooleanAttributeFromToken('is_root');
  }
  get performsNonLearnerFunction() {
    return this.getBooleanAttributeFromToken('performs_non_learner_function');
  }
  get canCreateOrUpdateUserInAnySchool() {
    return this.getBooleanAttributeFromToken('can_create_or_update_user_in_any_school');
  }
  async isDirectingSchool(school) {
    const user = await this.getModel();
    const ids = user.hasMany('directedSchools').ids();
    return ids.includes(school.get('id'));
  }
  async isAdministeringSchool(school) {
    const user = await this.getModel();
    const ids = user.hasMany('administeredSchools').ids();
    return ids.includes(school.get('id'));
  }
  async isDirectingProgramInSchool(school) {
    const user = await this.getModel();
    const schoolProgramIds = school.hasMany('programs').ids();

    const ids = user.hasMany('directedPrograms').ids();
    const matches = ids.filter((id) => schoolProgramIds.includes(id));

    return matches.length > 0;
  }
  async isDirectingCourseInSchool(school) {
    const user = await this.getModel();
    const schoolCourseIds = school.hasMany('courses').ids();

    const ids = user.hasMany('directedCourses').ids();
    const matches = ids.filter((id) => schoolCourseIds.includes(id));

    return matches.length > 0;
  }
  async isAdministeringCourseInSchool(school) {
    const user = await this.getModel();
    const schoolCourseIds = school.hasMany('courses').ids();

    const ids = user.hasMany('administeredCourses').ids();
    const matches = ids.filter((id) => schoolCourseIds.includes(id));

    return matches.length > 0;
  }
  async isAdministeringSessionInSchool(school) {
    const user = await this.getModel();
    const schoolCourseIds = school.hasMany('courses').ids();

    const sessions = await user.get('administeredSessions');
    const matches = sessions.filter((session) =>
      schoolCourseIds.includes(session.belongsTo('course').id()),
    );

    return matches.length > 0;
  }
  async isTeachingCourseInSchool(school) {
    const schoolCourseIds = school.hasMany('courses').ids();
    if (!schoolCourseIds.length) {
      return false;
    }
    const courses = await this.getAllInstructedCourses();
    const matches = courses.filter((course) => schoolCourseIds.includes(course.id));

    return matches.length > 0;
  }
  async isAdministeringCurriculumInventoryReportInSchool(school) {
    const user = await this.getModel();
    const schoolProgramIds = school.hasMany('programs').ids();

    const reports = await user.get('administeredCurriculumInventoryReports');
    const matches = reports.filter((report) =>
      schoolProgramIds.includes(report.belongsTo('program').id()),
    );

    return matches.length > 0;
  }
  async isDirectingCourse(course) {
    const user = await this.getModel();

    const ids = user.hasMany('directedCourses').ids();

    return ids.includes(course.get('id'));
  }
  async isAdministeringCourse(course) {
    const user = await this.getModel();

    const ids = user.hasMany('administeredCourses').ids();

    return ids.includes(course.get('id'));
  }
  async isAdministeringSessionInCourse(course) {
    const user = await this.getModel();

    const sessions = await user.get('administeredSessions');
    const matches = sessions.filter(
      (session) => course.get('id') === session.belongsTo('course').id(),
    );

    return matches.length > 0;
  }
  async isTeachingCourse(course) {
    const courses = await this.getAllInstructedCourses();
    const matches = courses.filter((c) => c.id === course.id);
    return matches.length > 0;
  }
  async isAdministeringSession(session) {
    const user = await this.getModel();

    const ids = user.hasMany('administeredSessions').ids();

    return ids.includes(session.get('id'));
  }
  async isTeachingSession(session) {
    const sessions = await this.getAllInstructedSessions();
    const matches = sessions.filter((s) => s.id === session.id);

    return matches.length > 0;
  }
  async isDirectingProgram(program) {
    const user = await this.getModel();

    const ids = user.hasMany('directedPrograms').ids();

    return ids.includes(program.get('id'));
  }
  async isDirectingProgramYearInProgram(program) {
    const user = await this.getModel();

    const programYears = await user.get('programYears');
    const matches = programYears.filter(
      (programYear) => program.get('id') === programYear.belongsTo('program').id(),
    );

    return matches.length > 0;
  }
  async isDirectingProgramYear(programYear) {
    const user = await this.getModel();

    const ids = user.hasMany('programYears').ids();

    return ids.includes(programYear.get('id'));
  }
  async isAdministeringCurriculumInventoryReport(report) {
    const user = await this.getModel();

    const ids = user.hasMany('administeredCurriculumInventoryReports').ids();

    return ids.includes(report.get('id'));
  }
  async getRolesInSchool(school, rolesToCheck = []) {
    const roles = [];
    if (rolesToCheck.includes('SCHOOL_DIRECTOR') && (await this.isDirectingSchool(school))) {
      roles.push('SCHOOL_DIRECTOR');
    }
    if (
      rolesToCheck.includes('SCHOOL_ADMINISTRATOR') &&
      (await this.isAdministeringSchool(school))
    ) {
      roles.push('SCHOOL_ADMINISTRATOR');
    }
    if (
      rolesToCheck.includes('PROGRAM_DIRECTOR') &&
      (await this.isDirectingProgramInSchool(school))
    ) {
      roles.push('PROGRAM_DIRECTOR');
    }
    if (
      rolesToCheck.includes('COURSE_DIRECTOR') &&
      (await this.isDirectingCourseInSchool(school))
    ) {
      roles.push('COURSE_DIRECTOR');
    }
    if (
      rolesToCheck.includes('COURSE_ADMINISTRATOR') &&
      (await this.isAdministeringCourseInSchool(school))
    ) {
      roles.push('COURSE_ADMINISTRATOR');
    }
    if (
      rolesToCheck.includes('SESSION_ADMINISTRATOR') &&
      (await this.isAdministeringSessionInSchool(school))
    ) {
      roles.push('SESSION_ADMINISTRATOR');
    }
    if (
      rolesToCheck.includes('COURSE_INSTRUCTOR') &&
      (await this.isTeachingCourseInSchool(school))
    ) {
      roles.push('COURSE_INSTRUCTOR');
    }
    if (
      rolesToCheck.includes('CURRICULUM_INVENTORY_REPORT_ADMINISTRATOR') &&
      (await this.isAdministeringCurriculumInventoryReportInSchool(school))
    ) {
      roles.push('CURRICULUM_INVENTORY_REPORT_ADMINISTRATOR');
    }

    return roles;
  }
  async getRolesInCourse(course, rolesToCheck = []) {
    const roles = [];
    if (rolesToCheck.includes('COURSE_DIRECTOR') && (await this.isDirectingCourse(course))) {
      roles.push('COURSE_DIRECTOR');
    }
    if (
      rolesToCheck.includes('COURSE_ADMINISTRATOR') &&
      (await this.isAdministeringCourse(course))
    ) {
      roles.push('COURSE_ADMINISTRATOR');
    }
    if (
      rolesToCheck.includes('SESSION_ADMINISTRATOR') &&
      (await this.isAdministeringSessionInCourse(course))
    ) {
      roles.push('SESSION_ADMINISTRATOR');
    }
    if (rolesToCheck.includes('COURSE_INSTRUCTOR') && (await this.isTeachingCourse(course))) {
      roles.push('COURSE_INSTRUCTOR');
    }

    return roles;
  }
  async getRolesInSession(session, rolesToCheck = []) {
    const roles = [];
    if (
      rolesToCheck.includes('SESSION_ADMINISTRATOR') &&
      (await this.isAdministeringSession(session))
    ) {
      roles.push('SESSION_ADMINISTRATOR');
    }
    if (rolesToCheck.includes('SESSION_INSTRUCTOR') && (await this.isTeachingSession(session))) {
      roles.push('SESSION_INSTRUCTOR');
    }

    return roles;
  }
  async getRolesInProgram(program, rolesToCheck = []) {
    const roles = [];
    if (rolesToCheck.includes('PROGRAM_DIRECTOR') && (await this.isDirectingProgram(program))) {
      roles.push('PROGRAM_DIRECTOR');
    }
    if (
      rolesToCheck.includes('PROGRAM_YEAR_DIRECTOR') &&
      (await this.isDirectingProgramYearInProgram(program))
    ) {
      roles.push('PROGRAM_YEAR_DIRECTOR');
    }

    return roles;
  }
  async getRolesInProgramYear(programYear, rolesToCheck = []) {
    const roles = [];
    if (
      rolesToCheck.includes('PROGRAM_YEAR_DIRECTOR') &&
      (await this.isDirectingProgramYear(programYear))
    ) {
      roles.push('PROGRAM_YEAR_DIRECTOR');
    }

    return roles;
  }
  async getRolesInCurriculumInventoryReport(report, rolesToCheck = []) {
    const roles = [];
    if (
      rolesToCheck.includes('CURRICULUM_INVENTORY_REPORT_ADMINISTRATOR') &&
      (await this.isAdministeringCurriculumInventoryReport(report))
    ) {
      roles.push('CURRICULUM_INVENTORY_REPORT_ADMINISTRATOR');
    }

    return roles;
  }

  /**
   * Returns a list of all courses that this user is instructing in.
   * This entails the following user-to-course relationship paths:
   * - instructor -> ILMs-> sessions -> courses
   * - instructor -> instructor-groups -> ILMs -> sessions -> courses
   * - instructor -> learner-groups -> ILMs -> sessions -> courses
   * - instructor -> offerings -> sessions -> courses
   * - instructor -> instructor-groups -> offerings -> sessions -> courses
   * - instructor -> learner-groups -> offerings -> sessions -> courses
   */
  async getAllInstructedCourses() {
    const sessions = await this.getAllInstructedSessions();
    const courses = await Promise.all(sessions.map((s) => s.course));
    return uniqueValues(courses);
  }

  /**
   * Returns a list of all sessions that this user is instructing in.
   * This entails the following user-to-session relationship paths:
   * - instructor -> ILMs-> sessions
   * - instructor -> instructor-groups -> ILMs -> sessions
   * - instructor -> learner-groups -> ILMs -> sessions
   * - instructor -> offerings -> sessions
   * - instructor -> instructor-groups -> offerings -> sessions
   * - instructor -> learner-groups -> offerings -> sessions
   */
  async getAllInstructedSessions() {
    const user = await this.getModel();
    const instructedIlms = await user.instructorIlmSessions;
    const instructedLearnerGroups = await user.instructedLearnerGroups;
    const instructorGroups = await user.instructorGroups;

    // instructor -> ILMs -> sessions
    const instructedIlmsSessions = await Promise.all(instructedIlms.map((t) => t.session));

    // instructor -> instructor-groups -> ILMs -> sessions
    const instructorGroupsIlms = await Promise.all(instructorGroups.map((i) => i.ilmSessions));
    const instructorGroupsIlmsSessions = await Promise.all(
      instructorGroupsIlms.flat().map((ilm) => ilm.session),
    );

    // instructor -> learner-groups -> ILMs -> sessions
    const learnerGroupsIlms = await Promise.all(instructedLearnerGroups.map((i) => i.ilmSessions));
    const learnerGroupsIlmsSessions = await Promise.all(
      learnerGroupsIlms.flat().map((ilm) => ilm.session),
    );

    // instructor -> offerings -> sessions
    const instructedOfferings = await user.instructedOfferings;
    const instructedOfferingsSessions = await Promise.all(
      instructedOfferings.map((t) => t.session),
    );

    // instructor -> instructor-groups -> offerings -> sessions
    const instructorGroupsOfferings = await Promise.all(instructorGroups.map((i) => i.offerings));
    const instructorGroupsOfferingsSessions = await Promise.all(
      instructorGroupsOfferings.flat().map((offering) => offering.session),
    );

    // instructor -> learner-groups -> offerings -> sessions
    const learnerGroupsOfferings = await Promise.all(
      instructedLearnerGroups.map((i) => i.offerings),
    );
    const learnerGroupsOfferingsSessions = await Promise.all(
      learnerGroupsOfferings.flat().map((offering) => offering.session),
    );

    return uniqueValues(
      [
        ...instructedIlmsSessions,
        ...instructorGroupsIlmsSessions,
        ...learnerGroupsIlmsSessions,
        ...instructedOfferingsSessions,
        ...instructorGroupsOfferingsSessions,
        ...learnerGroupsOfferingsSessions,
      ].filter(Boolean),
    );
  }
}