Source

api/verification/VerificationRequestCollection.methods.ts

import { Meteor } from 'meteor/meteor';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { CallPromiseMixin } from 'meteor/didericis:callpromise-mixin';
import { VerificationRequests } from './VerificationRequestCollection';
import { Feeds } from '../feed/FeedCollection';
import { OpportunityInstances } from '../opportunity/OpportunityInstanceCollection';
import { Opportunities } from '../opportunity/OpportunityCollection';
import { AcademicTerms } from '../academic-term/AcademicTermCollection';
import { Users } from '../user/UserCollection';
import { ROLE } from '../role/Role';

/**
 * The ValidatedMethod for updating the status of VerificationRequests.
 * Passed an object with fields:
 * id: the verificationRequestID
 * status: boolean
 * processed: a project object.
 * @memberOf api/verification
 */
export const verificationRequestsUpdateStatusMethod = new ValidatedMethod({
  name: 'VerificationRequests.updateStatus',
  validate: null,
  mixins: [CallPromiseMixin],
  run(update) {
    // Verify that currently logged in user is an admin, advisor, or faculty. otherwise no update can occur.
    VerificationRequests.assertRole(Meteor.userId(), [ROLE.ADMIN, ROLE.ADVISOR, ROLE.FACULTY]);
    return VerificationRequests.updateStatus(update.id, update.status, update.processed);
  },
});

/**
 * Returns the opportunityInstanceID associated with the student and opportunity, or null if not found.
 * @param student The student.
 * @param opportunity The opportunity.
 * @param academicTerm The academicTerm
 * @returns The opportunityInstanceID, or null if it wasn't found.
 * @memberOf api/verification
 */
function getOpportunityInstanceID(student, opportunity, academicTerm) {
  const studentID = Users.getID(student);
  const opportunityID = Opportunities.getID(opportunity);
  const termID = AcademicTerms.getID(academicTerm);
  const opportunityInstances = OpportunityInstances.findNonRetired({ opportunityID, studentID, termID });
  return (opportunityInstances.length > 0) ? opportunityInstances[0]._id : null;
}

/**
 * This Meteor Method processes a request to verify an opportunity for a given user from the VerificationEvent page.
 * The method is passed a student, opportunity, and academicTerm, which should normally be valid.
 * Processing this request involves the following:
 *   * If the student does not have an Opportunity Instance for this opportunity and academicTerm, then one is created
 *     for them.
 *   * If the student has not already submitted a Verification Request for their Opportunity Instance, then one is
 *     created for them.
 *   * If the student has already been verified for this Opportunity, then nothing more is done.
 *   * Once the OpportunityInstance and VerificationRequest exist, then they are updated to indicate that they have
 *     been verified if they are not already verified.
 *   * A status string is returned to the caller to indicate the result of processing.
 * Only faculty, advisors, and admins can invoke this method.
 * @memberOf api/verification
 */
export const processVerificationEventMethod = new ValidatedMethod({
  name: 'VerificationRequests.processVerificationEvent',
  validate: null,
  mixins: [CallPromiseMixin],
  run({ student, opportunity, academicTerm, verified = false }: { student: string; opportunity: string; academicTerm: string; verified?: boolean; }) {
    // Define a string to hold the result of this process.
    let resultMessage = '';

    // Verify that currently logged in user is an admin, advisor, or faculty. otherwise no verification can occur.
    VerificationRequests.assertRole(Meteor.userId(), [ROLE.ADMIN, ROLE.ADVISOR, ROLE.FACULTY]);

    // Make sure there's an opportunity instance for this student.
    let opportunityInstanceID = getOpportunityInstanceID(student, opportunity, academicTerm);
    if (!opportunityInstanceID) {
      resultMessage += '  No opportunity instance found. Defining a new one.\n';
      opportunityInstanceID = OpportunityInstances.define({ academicTerm, opportunity, student, verified });
    }

    // Make sure there's a verification request for this opportunity instance.
    let verificationRequestID = VerificationRequests.findOne({ opportunityInstanceID });
    if (!verificationRequestID) {
      resultMessage += '  No verification request found. Defining a new one.\n';
      verificationRequestID = VerificationRequests.define({ student, opportunityInstance: opportunityInstanceID });
    }

    // If this event has already been verified, then return now.
    if (OpportunityInstances.findDoc(opportunityInstanceID).verified) {
      const opp = Opportunities.findDoc(opportunity);
      return `  Event ${opp.name} is already verified for ${student}\n`;
    }

    // Otherwise verify the opportunity instance and the VerificationRequestID.
    resultMessage += '  Setting the opportunity instance and verification request to verified.\n';
    OpportunityInstances.update(opportunityInstanceID, { verified: true });
    VerificationRequests.setVerified(verificationRequestID, Meteor.userId());

    // Create a Feed entry for this verification event.
    resultMessage += '  Creating a feed entry.\n';
    Feeds.define({ feedType: Feeds.VERIFIED_OPPORTUNITY, user: student, opportunity, academicTerm });

    return resultMessage;
  },
});

/**
 * This Meteor Method processes a command from the Pending Verification Request widget.
 * The method is passed a verificationRequestID, a command (ACCEPTED or REJECTED), and an optional feedback string.
 * The method returns a string indicating what happened.
 * Processing this request involves the following:
 *
 *   * Make sure user is a faculty, advisor, or admin. Otherwise throw error.
 *   * If verificationRequestID is not a valid ID, then throw error.
 *   * If command is not VerificationRequests.ACCEPTED or VerificationRequests.REJECTED, then returns an error string.
 *   * Creates a process object to record this command.
 *   * Updates the VerificationRequest with the new process object and the new status.
 *   * Creates a new Feed instance only if the request was accepted.
 *
 * Only faculty, advisors, and admins can invoke this method.
 * @memberOf api/verification
 */
export const processPendingVerificationMethod = new ValidatedMethod({
  name: 'VerificationRequests.processPendingVerification',
  validate: null,
  mixins: [CallPromiseMixin],
  run({ verificationRequestID, command, feedback }) {
    // Verify that currently logged in user is an admin, advisor, or faculty. otherwise no verification can occur.
    VerificationRequests.assertRole(Meteor.userId(), [ROLE.ADMIN, ROLE.ADVISOR, ROLE.FACULTY]);

    // Verify that the ID is valid.
    const requestDoc = VerificationRequests.findDoc(verificationRequestID);

    // Verify that the command is VerificationRequests.ACCEPTED or REJECTED.
    if ((command !== VerificationRequests.ACCEPTED) && (command !== VerificationRequests.REJECTED)) {
      throw new Meteor.Error(`VerificationRequest command is invalid: ${command}`);
    }

    // Update the opportunityInstance corresponding to this verification request.
    const verified = (command === VerificationRequests.ACCEPTED);
    OpportunityInstances.update(requestDoc.opportunityInstanceID, { verified });

    // Update the verification request.
    VerificationRequests.setVerificationStatus(verificationRequestID, Meteor.userId(), command, feedback);

    // Create a feed entry only if it was verified
    const student = Users.getProfile(requestDoc.studentID).username;
    if (verified) {
      const opportunityInstanceID = requestDoc.opportunityInstanceID;
      const opportunity = OpportunityInstances.getOpportunityDoc(opportunityInstanceID)._id;
      const academicTermDoc = OpportunityInstances.getAcademicTermDoc(opportunityInstanceID);
      const academicTerm = `${academicTermDoc.term}-${academicTermDoc.year}`;
      Feeds.define({ feedType: Feeds.VERIFIED_OPPORTUNITY, user: student, opportunity, academicTerm });
    }
    return `Verification request for ${student} was processed`;
  },
});