Source

api/user/StudentProfileCollection.ts

import _ from 'lodash';
import { Meteor } from 'meteor/meteor';
import SimpleSchema from 'simpl-schema';
import { Roles } from 'meteor/alanning:roles';
import { ReactiveAggregate } from 'meteor/jcbernack:reactive-aggregate';
import { Reviews } from '../review/ReviewCollection';
import { VerificationRequests } from '../verification/VerificationRequestCollection';
import BaseProfileCollection, { defaultProfilePicture } from './BaseProfileCollection';
import { CareerGoals } from '../career/CareerGoalCollection';
import { Courses } from '../course/CourseCollection';
import { CourseInstances } from '../course/CourseInstanceCollection';
import { Interests } from '../interest/InterestCollection';
import { Opportunities } from '../opportunity/OpportunityCollection';
import { OpportunityInstances } from '../opportunity/OpportunityInstanceCollection';
import { AcademicTerms } from '../academic-term/AcademicTermCollection';
import { Users } from './UserCollection';
import { ROLE } from '../role/Role';
import { getProjectedICE, getEarnedICE } from '../ice/IceProcessor';
import { AcademicTerm, StudentProfileDefine, StudentProfileUpdate, StudentProfileUpdateData } from '../../typings/radgrad';
import { ProfileInterests } from './profile-entries/ProfileInterestCollection';
import { ProfileCareerGoals } from './profile-entries/ProfileCareerGoalCollection';
import { ProfileCourses } from './profile-entries/ProfileCourseCollection';
import { ProfileOpportunities } from './profile-entries/ProfileOpportunityCollection';
import { checkUsername } from './utilities/profile';

/**
 * Represents a Student Profile.
 * @extends api/user.BaseProfileCollection
 * @memberOf api/user
 */
class StudentProfileCollection extends BaseProfileCollection {
  constructor() {
    super(
      'StudentProfile',
      new SimpleSchema({
        level: { type: SimpleSchema.Integer, min: 1, max: 6 },
        declaredAcademicTermID: { type: SimpleSchema.RegEx.Id, optional: true }, // TODO do we need this anymore?
        isAlumni: Boolean,
        shareLevel: { type: Boolean, optional: true },
        shareICE: { type: Boolean, optional: true },
        lastRegistrarLoad: { type: String, optional: true },
        lastLeveledUp: { type: String, optional: true },
      }),
    );
    this.defineSchema = new SimpleSchema({
      username: String,
      firstName: String,
      lastName: String,
      picture: { type: String, optional: true },
      website: { type: String, optional: true },
      interests: { type: Array, optional: true },
      'interests.$': String,
      careerGoals: { type: Array, optional: true },
      'careerGoals.$': String,
      retired: { type: Boolean, optional: true },
      level: { type: SimpleSchema.Integer, min: 1, max: 6 },
      declaredAcademicTerm: { type: String, optional: true },
      isAlumni: { type: Boolean, optional: true },
      profileCourses: { type: Array, optional: true },
      'profileCourses.$': String,
      profileOpportunities: { type: Array, optional: true },
      'profileOpportunities.$': String,
      sharePicture: { type: Boolean, optional: true },
      shareWebsite: { type: Boolean, optional: true },
      shareInterests: { type: Boolean, optional: true },
      shareCareerGoals: { type: Boolean, optional: true },
      shareOpportunities: { type: Boolean, optional: true },
      shareCourses: { type: Boolean, optional: true },
      shareLevel: { type: Boolean, optional: true },
      shareICE: { type: Boolean, optional: true },
      lastRegistrarLoad: { type: String, optional: true },
      lastLeveledUp: { type: String, optional: true },
      acceptedTermsAndConditions: { type: String, optional: true },
      refusedTermsAndConditions: { type: String, optional: true },
      lastVisited: { type: Object, optional: true, blackbox: true },
    });
    this.updateSchema = new SimpleSchema({
      firstName: { type: String, optional: true },
      lastName: { type: String, optional: true },
      picture: { type: String, optional: true },
      website: { type: String, optional: true },
      interests: { type: Array, optional: true },
      'interests.$': String,
      careerGoals: { type: Array, optional: true },
      'careerGoals.$': String,
      retired: { type: Boolean, optional: true },
      level: { type: SimpleSchema.Integer, min: 1, max: 6 },
      declaredAcademicTermID: { type: String, optional: true },
      isAlumni: { type: Boolean, optional: true },
      profileCourses: { type: Array, optional: true },
      'profileCourses.$': String,
      profileOpportunities: { type: Array, optional: true },
      'profileOpportunities.$': String,
      sharePicture: { type: Boolean, optional: true },
      shareWebsite: { type: Boolean, optional: true },
      shareInterests: { type: Boolean, optional: true },
      shareCareerGoals: { type: Boolean, optional: true },
      shareOpportunities: { type: Boolean, optional: true },
      shareCourses: { type: Boolean, optional: true },
      shareLevel: { type: Boolean, optional: true },
      shareICE: { type: Boolean, optional: true },
      lastRegistrarLoad: { type: String, optional: true },
      lastLeveledUp: { type: String, optional: true },
      acceptedTermsAndConditions: { type: String, optional: true },
      refusedTermsAndConditions: { type: String, optional: true },
      lastVisited: { type: Object, optional: true, blackbox: true },
    });
  }

  /**
   * Defines the profile associated with a Student.
   * The username does not need to be defined in Meteor Accounts yet, but it must be a unique Slug.
   * @param username The username string associated with this profile, which should be an email.
   * @param firstName The first name.
   * @param lastName The last name.
   * @param picture The URL to their picture. (optional, defaults to a default picture.)
   * @param website The URL to their personal website (optional).
   * @param interests An array of interests. (optional)
   * @param careerGoals An array of career goals. (optional)
   * @param level An integer between 1 and 6 indicating the student's level.
   * @param retired boolean. (optional defaults to false)
   * @param declaredAcademicTerm An optional string indicating the student's declared academic term.
   * @param isAlumni An optional boolean indicating if this student has graduated. Defaults to false.
   * @param sharePicture An optional boolean indicating if this student is sharing their picture. Defaults to false.
   * @param shareWebsite An optional boolean indicating if this student is sharing their website. Defaults to false.
   * @param shareInterests An optional boolean indicating if this student is sharing their interests. Defaults to false.
   * @param shareCareerGoals An optional boolean indicating if this student is sharing their career goals. Defaults to false.
   * @param shareCourses An optional boolean indicating if this student is sharing their courses. Defaults to false.
   * @param shareOpportunities An optional boolean indicating if this student is sharing their opportunities. Defaults to false.
   * @param shareLevel An optional boolean indicating if this student is sharing their level. Defaults to false.
   * @param shareICE An optional boolean indicating if this student is sharing their ICE points. Defaults to false.
   * @param lastVisited An optional object with PAGEIDS for keys and strings in format "YYYY-MM-DD" for values. Defaults to an empty object.
   * @throws { Meteor.Error } If username has been previously defined, or if any interests, careerGoals, level,
   * or declaredAcademicTerm are invalid.
   * @return { String } The docID of the StudentProfile.
   */

  public define({
    username,
    firstName,
    lastName,
    picture = defaultProfilePicture,
    website,
    interests,
    careerGoals,
    level,
    declaredAcademicTerm,
    profileCourses = [],
    profileOpportunities = [],
    isAlumni = false,
    retired = false,
    sharePicture = false,
    shareWebsite = false,
    shareInterests = false,
    shareCareerGoals = false,
    shareCourses = false,
    shareOpportunities = false,
    shareLevel = false,
    shareICE = false,
    lastRegistrarLoad,
    lastVisited = {},
    lastLeveledUp,
    acceptedTermsAndConditions,
    refusedTermsAndConditions,
  }: StudentProfileDefine) {
    if (Meteor.isServer) {
      // Validate parameters.
      const declaredAcademicTermID = declaredAcademicTerm ? AcademicTerms.getID(declaredAcademicTerm) : undefined;
      this.assertValidLevel(level);
      if (!_.isBoolean(isAlumni)) {
        throw new Meteor.Error(`Invalid isAlumni: ${isAlumni}`);
      }
      checkUsername(username);
      const role = isAlumni ? ROLE.ALUMNI : ROLE.STUDENT;
      const profileID = this.collection.insert({
        username,
        firstName,
        lastName,
        role,
        picture,
        website,
        level,
        declaredAcademicTermID,
        isAlumni,
        userID: this.getFakeUserId(),
        retired,
        sharePicture,
        shareWebsite,
        shareInterests,
        shareCareerGoals,
        shareCourses,
        shareOpportunities,
        shareLevel,
        shareICE,
        lastRegistrarLoad,
        lastVisited,
        lastLeveledUp,
        acceptedTermsAndConditions,
        refusedTermsAndConditions,
      });
      const userID = Users.define({ username, role });
      this.collection.update(profileID, { $set: { userID } });
      if (interests) {
        interests.forEach((interest) => ProfileInterests.define({ interest, username }));
      }
      if (careerGoals) {
        careerGoals.forEach((careerGoal) => ProfileCareerGoals.define({
          careerGoal,
          username,
        }));
      }
      if (profileCourses) {
        profileCourses.forEach((course) => ProfileCourses.define({ course, username }));
      }
      if (profileOpportunities) {
        profileOpportunities.forEach((opportunity) =>
          ProfileOpportunities.define({
            opportunity,
            username,
          }),
        );
      }
      return profileID;
    }
    return undefined;
  }

  /**
   * Updates the StudentProfile.
   * You cannot change the username or role once defined. (You can implicitly change the role by setting isAlumni).
   * Users in ROLE.STUDENT cannot change their level or isAlumni setting.
   * @param docID
   * @param firstName
   * @param lastName
   * @param picture
   * @param website
   * @param interests array of interest slugs or IDs.
   * @param careerGoals
   * @param level
   * @param declaredAcademicTerm
   * @param isAlumni
   * @param retired
   * @param sharePicture
   * @param shareWebsite
   * @param shareInterests
   * @param shareCareerGoals
   * @param shareCourses
   * @param shareOpportunities
   * @param shareLevel
   * @param shareICE
   */
  public update(
    docID,
    {
      firstName,
      lastName,
      picture,
      website,
      interests,
      careerGoals,
      level,
      profileCourses,
      profileOpportunities,
      declaredAcademicTerm,
      isAlumni,
      retired,
      courseExplorerFilter,
      opportunityExplorerSortOrder,
      sharePicture,
      shareWebsite,
      shareCareerGoals,
      shareCourses,
      shareInterests,
      shareOpportunities,
      shareLevel,
      shareICE,
      lastRegistrarLoad,
      lastLeveledUp,
      acceptedTermsAndConditions,
      refusedTermsAndConditions,
    }: StudentProfileUpdate,
  ) {
    this.assertDefined(docID);
    const profile = this.findDoc(docID);
    const updateData: StudentProfileUpdateData = {};
    this.updateCommonFields(updateData, {
      firstName,
      lastName,
      picture,
      website,
      retired,
      courseExplorerFilter,
      opportunityExplorerSortOrder,
      shareWebsite,
      sharePicture,
      shareCareerGoals,
      shareCourses,
      shareInterests,
      shareOpportunities,
      acceptedTermsAndConditions,
      refusedTermsAndConditions,
    });
    if (declaredAcademicTerm) {
      updateData.declaredAcademicTermID = AcademicTerms.getID(declaredAcademicTerm);
    }
    // Only Admins and Advisors can update the isAlumni and level fields.
    // Or if no one is logged in when this is executed (i.e. for testing) then it's cool.
    if (Meteor.isTest || !Meteor.userId() || this.hasRole(Meteor.userId(), [ROLE.ADMIN, ROLE.ADVISOR])) {
      const userID = this.findDoc(docID).userID;
      if (_.isBoolean(isAlumni)) {
        updateData.isAlumni = isAlumni;
        if (isAlumni) {
          updateData.role = ROLE.ALUMNI;
          Roles.createRole(ROLE.ALUMNI, { unlessExists: true });
          Roles.addUsersToRoles(userID, [ROLE.ALUMNI]);
          Roles.removeUsersFromRoles(userID, [ROLE.STUDENT]);
        } else {
          updateData.role = ROLE.STUDENT;
          Roles.createRole(ROLE.STUDENT, { unlessExists: true });
          Roles.addUsersToRoles(userID, [ROLE.STUDENT]);
          Roles.removeUsersFromRoles(userID, [ROLE.ALUMNI]);
        }
      }
      if (level) {
        this.assertValidLevel(level);
        updateData.level = level;
      }
      if (_.isBoolean(retired)) {
        updateData.retired = retired;
      }
    }
    if (_.isBoolean(shareCourses)) {
      updateData.shareCourses = shareCourses;
    }
    if (_.isBoolean(shareOpportunities)) {
      updateData.shareOpportunities = shareOpportunities;
    }
    if (_.isBoolean(shareLevel)) {
      updateData.shareLevel = shareLevel;
    }
    if (_.isBoolean(shareICE)) {
      updateData.shareICE = shareICE;
    }
    if (lastRegistrarLoad) {
      updateData.lastRegistrarLoad = lastRegistrarLoad;
    }
    if (lastLeveledUp) {
      updateData.lastLeveledUp = lastLeveledUp;
    }
    if (acceptedTermsAndConditions) {
      updateData.acceptedTermsAndConditions = acceptedTermsAndConditions;
    }
    if (refusedTermsAndConditions) {
      updateData.refusedTermsAndConditions = refusedTermsAndConditions;
    }
    // console.log('StudentProfile.update %o', updateData);
    this.collection.update(docID, { $set: updateData });
    // console.log(this.findDoc(docID));
    const username = profile.username;
    if (interests) {
      ProfileInterests.removeUser(username);
      interests.forEach((interest) => ProfileInterests.define({ interest, username, retired }));
    }
    if (careerGoals) {
      ProfileCareerGoals.removeUser(username);
      careerGoals.forEach((careerGoal) => ProfileCareerGoals.define({ careerGoal, username, retired }));
    }
    if (profileCourses) {
      ProfileCourses.removeUser(username);
      profileCourses.forEach((course) => ProfileCourses.define({ course, username, retired }));
    }
    if (profileOpportunities) {
      ProfileOpportunities.removeUser(username);
      profileOpportunities.forEach((opportunity) => ProfileOpportunities.define({ opportunity, username, retired }));
    }
    if (_.isBoolean(retired)) {
      const userID = profile.userID;
      // retire the CourseInstances, OpportunityInstances, Reviews, and VerificationRequests
      const cis = CourseInstances.find({ userID }).fetch();
      cis.forEach((ci) => CourseInstances.update(ci._id, { retired }));
      const ois = OpportunityInstances.find({ userID }).fetch();
      ois.forEach((oi) => OpportunityInstances.update(oi._id, { retired }));
      const reviews = Reviews.find({ userID }).fetch();
      reviews.forEach((review) => Reviews.update(review._id, { retired }));
      const vrs = VerificationRequests.find({ userID }).fetch();
      vrs.forEach((vr) => VerificationRequests.update(vr._id, { retired }));
    }
  }

  /**
   * Removes this profile, given its profile ID.
   * Also removes this user from Meteor Accounts.
   * @param profileID The ID for this profile object.
   */
  public removeIt(profileID) {
    const docID = this.getID(profileID);
    if (this.isDefined(docID)) {
      return super.removeIt(docID);
    }
    return null;
  }

  /**
   * Asserts that level is an integer between 1 and 6.
   * @param level The level.
   */
  public assertValidLevel(level: number) {
    if (!_.isInteger(level) && !_.inRange(level, 1, 7)) {
      throw new Meteor.Error(`Level ${level} is not between 1 and 6.`);
    }
  }

  /**
   * Implementation of assertValidRoleForMethod. Asserts that userId is logged in as an Admin, Advisor or
   * Student.
   * This is used in the define, update, and removeIt Meteor methods associated with each class.
   * @param userId The userId of the logged in user. Can be null or undefined
   * @throws { Meteor.Error } If there is no logged in user, or the user is not an Admin or Advisor.
   */
  public assertValidRoleForMethod(userId: string) {
    this.assertRole(userId, [ROLE.ADMIN, ROLE.ADVISOR, ROLE.STUDENT]);
  }

  /**
   * Returns an array of strings, each one representing an integrity problem with this collection.
   * Returns an empty array if no problems were found.
   * Checks the profile common fields and the role..
   * @returns {Array} A (possibly empty) array of strings indicating integrity issues.
   */
  public checkIntegrity() {
    let problems = [];
    this.find().forEach((doc) => {
      problems = problems.concat(this.checkIntegrityCommonFields(doc));
      if (doc.role !== ROLE.STUDENT && doc.role !== ROLE.ALUMNI) {
        problems.push(`StudentProfile instance does not have ROLE.STUDENT or ROLE.ALUMNI: ${doc.username}`);
      }
      if (!_.isInteger(doc.level) && !_.inRange(doc.level, 1, 7)) {
        problems.push(`Level ${doc.level} is not an integer between 1 and 6 in ${doc.username}`);
      }
      if (!_.isBoolean(doc.isAlumni)) {
        problems.push(`Invalid isAlumni: ${doc.isAlumni} in ${doc.username}`);
      }
      if (doc.declaredAcademicTermID && !AcademicTerms.isDefined(doc.declaredAcademicTermID)) {
        problems.push(`Bad termID: ${doc.declaredAcademicTermID} in ${doc.username}`);
      }
    });
    return problems;
  }

  /**
   * Returns an ICE object with the total earned course and opportunity ICE values.
   * @param user The student (username or userID).
   * @throws {Meteor.Error} If userID is not defined.
   */
  public getEarnedICE(user: string) {
    const studentID = Users.getID(user);
    const courseDocs = CourseInstances.findNonRetired({ studentID });
    const oppDocs = OpportunityInstances.findNonRetired({ studentID });
    return getEarnedICE(courseDocs.concat(oppDocs));
  }

  /**
   * Returns an ICE object with the total projected course and opportunity ICE values.
   * @param user The student (username or userID).
   * @throws {Meteor.Error} If user is not defined.
   */
  public getProjectedICE(user: string) {
    const studentID = Users.getID(user);
    const courseDocs = CourseInstances.findNonRetired({ studentID });
    const oppDocs = OpportunityInstances.findNonRetired({ studentID });
    return getProjectedICE(courseDocs.concat(oppDocs));
  }

  /**
   * Returns true if projected innovation, competency, and experience points are all 100 or above.
   * @param {string} user The username.
   * @returns {boolean} True if degree plan is complete.
   */
  public isDegreePlanComplete(user: string) {
    const projectedICE = this.getProjectedICE(user);
    return ((projectedICE.c >= 100) && (projectedICE.e >= 100) && (projectedICE.i >= 100));
  }

  /**
   * Returns an array of courseIDs that this user has taken (or plans to take) based on their courseInstances.
   * @param userID The userID.
   */
  public getCourseIDs(user: string) {
    const userID = Users.getID(user);
    const courseInstanceDocs = CourseInstances.findNonRetired({ userID });
    const courseIDs = courseInstanceDocs.map((doc) => doc.courseID);
    return courseIDs; // allow for multiple 491 or 499 classes.
    // return _.uniq(courseIDs);
  }

  public getLastAcademicTerm(user: string): AcademicTerm {
    // console.log('getLastAcademicTerm', user);
    const studentID = Users.getID(user);
    let lastAcademicTerm = AcademicTerms.find({ termNumber: 0 }).fetch()[0];
    const cis = CourseInstances.find({ studentID }).fetch();
    cis.forEach((ci) => {
      const term = AcademicTerms.findDoc(ci.termID);
      if (term.termNumber > lastAcademicTerm.termNumber) {
        lastAcademicTerm = term;
      }
    });
    const ois = OpportunityInstances.find({ studentID }).fetch();
    ois.forEach((oi) => {
      const term = AcademicTerms.findDoc(oi.termID);
      if (term.termNumber > lastAcademicTerm.termNumber) {
        lastAcademicTerm = term;
      }
    });
    return lastAcademicTerm;
  }

  /**
   * Updates user's level.
   * @param user The user (username or userID).
   * @param level The new level.
   */
  public setLevel(user: string, level: number) {
    const id = this.getID(user);
    this.collection.update({ _id: id }, { $set: { level } });
  }

  public publish() {
    if (Meteor.isServer) {
      // console.log('StudentProfileCollection.publish');
      const collection = this.collection;
      Meteor.publish(this.collectionName, function () {
        const userID = Meteor.userId();
        ReactiveAggregate(this, collection, [
          {
            $project: {
              username: 1,
              firstName: 1,
              lastName: 1,
              role: 1,
              picture: {
                $cond: [
                  {
                    $or: [{ $ifNull: ['$sharePicture', false] }, { $eq: [userID, '$userID'] }, { $eq: [Roles.userIsInRole(userID, [ROLE.ADMIN, ROLE.ADVISOR, ROLE.FACULTY]), true] }],
                  },
                  '$picture',
                  '/images/default-profile-picture.png',
                ],
              },
              website: {
                $cond: [
                  {
                    $or: [{ $ifNull: ['$shareWebsite', false] }, { $eq: [userID, '$userID'] }, { $eq: [Roles.userIsInRole(userID, [ROLE.ADMIN, ROLE.ADVISOR, ROLE.FACULTY]), true] }],
                  },
                  '$website',
                  '',
                ],
              },
              userID: 1,
              retired: 1,
              level: {
                $cond: [
                  {
                    $or: [{ $ifNull: ['$shareLevel', false] }, { $eq: [userID, '$userID'] }, { $eq: [Roles.userIsInRole(userID, [ROLE.ADMIN, ROLE.ADVISOR, ROLE.FACULTY]), true] }],
                  },
                  '$level',
                  0,
                ],
              },
              declaredAcademicTermID: 1,
              isAlumni: 1,
              sharePicture: 1,
              shareWebsite: 1,
              shareInterests: 1,
              shareCareerGoals: 1,
              shareOpportunities: 1,
              shareCourses: 1,
              shareLevel: 1,
              shareICE: 1,
              optedIn: {
                $cond: [
                  {
                    $or: ['$sharePicture', '$shareWebsite', '$shareInterests', '$shareCareerGoals', '$shareOpportunities', '$shareCourses', '$shareLevel', '$shareICE'],
                  },
                  true,
                  false,
                ],
              },
              lastRegistrarLoad: 1,
              lastVisited: 1,
              lastLeveledUp: 1,
              acceptedTermsAndConditions: 1,
              refusedTermsAndConditions: 1,
            },
          },
        ]);
      });
    }
  }

  /**
   * Returns an object representing the StudentProfile docID in a format acceptable to define().
   * @param docID The docID of a StudentProfile
   * @returns { Object } An object representing the definition of docID.
   */
  public dumpOne(docID: string): StudentProfileDefine {
    const doc = this.findDoc(docID);
    const username = doc.username;
    const firstName = doc.firstName;
    const lastName = doc.lastName;
    const picture = doc.picture;
    const website = doc.website;
    const userID = Users.getID(username);
    const favInterests = ProfileInterests.findNonRetired({ userID });
    const interests = favInterests.map((fav) => Interests.findSlugByID(fav.interestID));
    const favCareerGoals = ProfileCareerGoals.findNonRetired({ userID });
    const careerGoals = favCareerGoals.map((fav) => CareerGoals.findSlugByID(fav.careerGoalID));
    const level = doc.level;
    const favCourses = ProfileCourses.findNonRetired({ userID });
    const profileCourses = favCourses.map((fav) => Courses.findSlugByID(fav.courseID));
    const favOpps = ProfileOpportunities.findNonRetired({ userID });
    const profileOpportunities = favOpps.map((fav) => Opportunities.findSlugByID(fav.opportunityID));
    const declaredAcademicTerm = doc.declaredAcademicTermID && AcademicTerms.findSlugByID(doc.declaredAcademicTermID);
    const isAlumni = doc.isAlumni;
    const retired = doc.retired;
    const sharePicture = doc.sharePicture;
    const shareWebsite = doc.shareWebsite;
    const shareInterests = doc.shareInterests;
    const shareCareerGoals = doc.shareCareerGoals;
    const shareOpportunities = doc.shareOpportunities;
    const shareCourses = doc.shareCourses;
    const shareLevel = doc.shareLevel;
    const shareICE = doc.shareICE;
    const lastRegistrarLoad = doc.lastRegistrarLoad;
    const lastLeveledUp = doc.lastLeveledUp;
    const acceptedTermsAndConditions = doc.acceptedTermsAndConditions;
    return {
      username,
      firstName,
      lastName,
      picture,
      website,
      interests,
      careerGoals,
      level,
      profileCourses,
      profileOpportunities,
      declaredAcademicTerm,
      isAlumni,
      retired,
      sharePicture,
      shareWebsite,
      shareInterests,
      shareCareerGoals,
      shareOpportunities,
      shareCourses,
      shareLevel,
      shareICE,
      lastRegistrarLoad,
      lastLeveledUp,
      acceptedTermsAndConditions,
    };
  }
}

/**
 * Provides the singleton instance this collection to all other entities.
 * @type {api/user.StudentProfileCollection}
 * @memberOf api/user
 */
export const StudentProfiles = new StudentProfileCollection();