Initial check in; All but the history pages working.
This commit is contained in:
60
imports/api/admin.js
Normal file
60
imports/api/admin.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import { _ } from 'underscore';
|
||||
import { Roles } from 'meteor/alanning:roles';
|
||||
|
||||
// console.log("Setting Up Admin...")
|
||||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.methods({
|
||||
'admin.fixRecords'(input) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
console.log("In Fix Records");
|
||||
console.log("Deleting invalid records...");
|
||||
|
||||
// Delete any records missing key fields.
|
||||
Meteor.Records.remove({serial: {$exists: false}});
|
||||
Meteor.Records.remove({deviceId: {$exists: false}});
|
||||
Meteor.Records.remove({endTime: {$exists: false}});
|
||||
|
||||
console.log("Consolidating records...");
|
||||
|
||||
let emails = _.uniq(Meteor.Records.find({}, {
|
||||
sort: {email: 1},
|
||||
fields: {email: true}
|
||||
}).fetch().map(function (x) {
|
||||
return x.email;
|
||||
}), true);
|
||||
|
||||
emails.forEach(email => {
|
||||
// Find all records for the user sorted from oldest to newest.
|
||||
let records = Meteor.Records.find({email}, {sort: {startTime: 1}}).fetch();
|
||||
let newRecords = [];
|
||||
let record = records[0];
|
||||
|
||||
for (let index = 1; index < records.length; index++) {
|
||||
let nextRecord = records[index];
|
||||
|
||||
if (record.deviceId === nextRecord.deviceId) {
|
||||
record.endTime = nextRecord.endTime;
|
||||
record.count += nextRecord.count;
|
||||
record.internalCount += nextRecord.internalCount;
|
||||
} else {
|
||||
if (!record.endTime) record.endTime = nextRecord.startTime;
|
||||
newRecords.push(record);
|
||||
record = nextRecord;
|
||||
}
|
||||
}
|
||||
newRecords.push(record);
|
||||
|
||||
Meteor.Records.remove({email});
|
||||
|
||||
for (let index = 0; index < newRecords.length; index++) {
|
||||
Meteor.Records.insert(newRecords[index]);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// console.log("Admin setup.")
|
||||
20
imports/api/asset-assignment-history.js
Normal file
20
imports/api/asset-assignment-history.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import {Mongo} from "meteor/mongo";
|
||||
|
||||
export const AssetAssignmentHistory = new Mongo.Collection('assetAssignmentHistory');
|
||||
|
||||
/*
|
||||
Maintains a historical record of asset assignments.
|
||||
assetKey: The MongoID of the asset. AssetID's could be reused, so this prevents that eventuality from messing up historical records.
|
||||
assetId: The asset's assigned ID (not a MongoID).
|
||||
serial: The asset's serial number (part of the device, or defined by the manufacturer). In some cases this may be a partial serial. It is not a unique identifier, but should be mostly unique. This might be undefined if a serial was never provided for the original asset.
|
||||
assetTypeName: The name of the asset type, or "UNK" if one could not be found (shouldn't happen). This is stored because these records could be kept longer than the assets in the system.
|
||||
assigneeType: One of 'Student' or 'Staff'.
|
||||
assigneeId: The MongoID of the student or staff the asset was assigned to.
|
||||
startDate: The date/time of the assignment.
|
||||
endDate: The date/time of the unassignment.
|
||||
comment: A text block detailing the reason for the unassignment of the device. Eg: 'Broke Screen' or 'End of year' or 'Not charging' or 'Needs replacement'.
|
||||
startCondition: One of the condition options: [New, Like New, Good, Okay, Damaged] (see assets.unassign for details).
|
||||
endCondition: One of the condition options: [New, Like New, Good, Okay, Damaged] (see assets.unassign for details).
|
||||
startConditionDetails: An optional text block for details on the condition.
|
||||
endConditionDetails: An optional text block for details on the condition.
|
||||
*/
|
||||
79
imports/api/asset-types.js
Normal file
79
imports/api/asset-types.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import {Mongo} from "meteor/mongo";
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import { check } from 'meteor/check';
|
||||
import { Roles } from 'meteor/alanning:roles';
|
||||
//import SimpleSchema from "simpl-schema";
|
||||
|
||||
// console.log("Setting Up Asset Types...")
|
||||
|
||||
//
|
||||
// An asset type is a specific type of equipment. Example: Lenovo 100e Chromebook.
|
||||
//
|
||||
export const AssetTypes = new Mongo.Collection('assetTypes');
|
||||
/*
|
||||
|
||||
const AssetTypesSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
label: "Model Name",
|
||||
optional: false,
|
||||
trim: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
label: "Description",
|
||||
optional: true,
|
||||
trim: true,
|
||||
defaultValue: "",
|
||||
},
|
||||
});
|
||||
|
||||
AssetTypes.attachSchema(AssetTypesSchema);
|
||||
*/
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// Drop any old indexes we no longer will use. Create indexes we need.
|
||||
try {AssetTypes._dropIndex("External ID")} catch(e) {}
|
||||
//AssetTypes.createIndex({name: "text"}, {name: "name", unique: false});
|
||||
//AssetTypes.createIndex({id: 1}, {name: "External ID", unique: true});
|
||||
|
||||
//Debug: Show all indexes.
|
||||
// AssetTypes.rawCollection().indexes((err, indexes) => {
|
||||
// console.log(indexes);
|
||||
// });
|
||||
|
||||
// This code only runs on the server
|
||||
Meteor.publish('assetTypes', function() {
|
||||
return AssetTypes.find({});
|
||||
});
|
||||
}
|
||||
Meteor.methods({
|
||||
'assetTypes.add'(name, description, year) {
|
||||
check(name, String);
|
||||
check(description, String);
|
||||
check(year, String);
|
||||
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
AssetTypes.insert({name, description, year});
|
||||
}
|
||||
},
|
||||
'assetTypes.update'(_id, name, description, year) {
|
||||
check(_id, String);
|
||||
check(name, String);
|
||||
check(description, String);
|
||||
check(year, String);
|
||||
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
AssetTypes.update({_id}, {$set: {name, description, year}});
|
||||
}
|
||||
},
|
||||
'assetTypes.remove'(_id) {
|
||||
check(_id, String);
|
||||
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
//TODO: Need to either remove all assets of this type, or change their type.
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// console.log("Asset types setup.")
|
||||
302
imports/api/assets.js
Normal file
302
imports/api/assets.js
Normal file
@@ -0,0 +1,302 @@
|
||||
import {Mongo} from "meteor/mongo";
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import { check } from 'meteor/check';
|
||||
import { Roles } from 'meteor/alanning:roles';
|
||||
//import SimpleSchema from "simpl-schema";
|
||||
import {AssetTypes} from "./asset-types";
|
||||
import {AssetAssignmentHistory} from "/imports/api/asset-assignment-history";
|
||||
|
||||
// console.log("Setting Up Assets...")
|
||||
|
||||
export const Assets = new Mongo.Collection('assets');
|
||||
export const conditions = ['New','Like New','Good','Okay','Damaged']
|
||||
|
||||
/*
|
||||
const AssetsSchema = new SimpleSchema({
|
||||
assetTypeId: {
|
||||
type: String,
|
||||
label: "Asset Type ID",
|
||||
optional: false,
|
||||
trim: true,
|
||||
},
|
||||
assetId: {
|
||||
type: String,
|
||||
label: "Asset ID",
|
||||
optional: false,
|
||||
trim: true,
|
||||
index: 1,
|
||||
unique: true
|
||||
},
|
||||
serial: {
|
||||
type: String,
|
||||
label: "Serial",
|
||||
optional: true,
|
||||
trim: false,
|
||||
index: 1,
|
||||
unique: false
|
||||
},
|
||||
assigneeId: { //Should be undefined or non-existent if not assigned.
|
||||
type: String,
|
||||
label: "Assignee ID",
|
||||
optional: true,
|
||||
},
|
||||
assigneeType: { // 0: Student, 1: Staff, Should be undefined or non-existent if not assigned.
|
||||
type: SimpleSchema.Integer,
|
||||
label: "Assignee Type",
|
||||
optional: true,
|
||||
min: 0,
|
||||
max: 1,
|
||||
exclusiveMin: false,
|
||||
exclusiveMax: false,
|
||||
},
|
||||
assignmentDate: {
|
||||
type: Date,
|
||||
label: "Assignment Date",
|
||||
optional: true,
|
||||
},
|
||||
condition: { //One of the condition options: [New, Like New, Good, Okay, Damaged] (see assets.unassign for details).
|
||||
type: String,
|
||||
label: "Condition",
|
||||
optional: false,
|
||||
},
|
||||
conditionDetails: { //An optional text block for details on the condition.
|
||||
type: String,
|
||||
label: "Condition Details",
|
||||
optional: true,
|
||||
}
|
||||
});
|
||||
Assets.attachSchema(AssetsSchema);
|
||||
*/
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// Drop any old indexes we no longer will use. Create indexes we need.
|
||||
//try {Assets._dropIndex("serial")} catch(e) {}
|
||||
Assets.createIndex({assetId: 1}, {name: "AssetID", unique: true});
|
||||
Assets.createIndex({serial: 1}, {name: "Serial", unique: false});
|
||||
|
||||
// This code only runs on the server
|
||||
Meteor.publish('assets', function() {
|
||||
return Assets.find({});
|
||||
});
|
||||
Meteor.publish('assetsAssignedTo', function(personId) {
|
||||
return Assets.find({assigneeId: personId});
|
||||
});
|
||||
}
|
||||
Meteor.methods({
|
||||
'assets.add'(assetTypeId, assetId, serial, condition, conditionDetails) {
|
||||
check(assetTypeId, String);
|
||||
check(assetId, String);
|
||||
check(serial, String);
|
||||
check(condition, String);
|
||||
if(conditionDetails) check(conditionDetails, String);
|
||||
|
||||
if(condition !== 'New' && condition !== 'Like New' && condition !== 'Good' && condition !== 'Okay' && condition !== 'Damaged') {
|
||||
//Should never happen.
|
||||
console.error("Invalid condition option in assets.add(..)");
|
||||
throw new Meteor.Error("Invalid condition option.");
|
||||
}
|
||||
|
||||
// Convert the asset ID's to uppercase for storage to make searching easier.
|
||||
assetId = assetId.toUpperCase();
|
||||
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
let assetType = AssetTypes.findOne({assetTypeId});
|
||||
|
||||
if(Assets.findOne({assetId})) {
|
||||
//return {error: true, errorType: 'duplicateAssetId'}
|
||||
throw new Meteor.Error("Duplicate Asset Id", "Cannot use the same asset ID twice.")
|
||||
}
|
||||
else if(serial) {
|
||||
Assets.insert({assetTypeId, assetId, serial, condition, conditionDetails});
|
||||
}
|
||||
else {
|
||||
Assets.insert({assetTypeId, assetId, condition, conditionDetails});
|
||||
}
|
||||
}
|
||||
else throw new Meteor.Error("User Permission Error");
|
||||
},
|
||||
'assets.update'(_id, assetTypeId, assetId, serial, condition, conditionDetails) {
|
||||
check(_id, String);
|
||||
check(assetTypeId, String);
|
||||
check(assetId, String);
|
||||
if(serial) check(serial, String);
|
||||
check(condition, String);
|
||||
if(conditionDetails) check(conditionDetails, String);
|
||||
|
||||
if(condition !== 'New' && condition !== 'Like New' && condition !== 'Good' && condition !== 'Okay' && condition !== 'Damaged') {
|
||||
//Should never happen.
|
||||
console.error("Invalid condition option in assets.update(..)");
|
||||
throw new Meteor.Error("Invalid condition option.");
|
||||
}
|
||||
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
//TODO: Need to first verify there are no checked out assets to the staff member.
|
||||
Assets.update({_id}, {$set: {assetTypeId, assetId, serial, condition, conditionDetails}});
|
||||
}
|
||||
else throw new Meteor.Error("User Permission Error");
|
||||
},
|
||||
'assets.remove'(_id) {
|
||||
check(_id, String);
|
||||
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
let asset = Assets.findOne({_id});
|
||||
|
||||
if(asset) {
|
||||
// Ensure the asset is not assigned still. Must unassign then remove. That allows us to maintain historical records for assignees.
|
||||
if(asset.assigneeId) {
|
||||
throw new Meteor.Error("Must unassign the asset before removal.");
|
||||
}
|
||||
else {
|
||||
Assets.remove({_id});
|
||||
}
|
||||
}
|
||||
else {
|
||||
//This should never happen.
|
||||
throw new Meteor.Error("Could not find the asset: " + _id);
|
||||
}
|
||||
}
|
||||
else throw new Meteor.Error("User Permission Error");
|
||||
},
|
||||
/**
|
||||
* Assigns the asset to the assignee. The assignee should either be a Student or Staff member.
|
||||
* @param assetId The Asset ID (eg: 'Z1Q') of the asset (asset.assetId).
|
||||
* @param assigneeType One of: 'Student', 'Staff'
|
||||
* @param assigneeId The Mongo ID of the Student or Staff (person._id).
|
||||
* @param condition One of the condition options: [New, Like New, Good, Okay, Damaged]. 'Like New' is defined as very minor cosmetic damage. 'Good' is defined as some cosmetic damage or very minor screen damage. 'Okay' is defined as significant cosmetic damage, or screen/keyboard/trackpad damage but is still useable. 'Damaged' indicates significant damage and the device should not be reissued until it is repaired.
|
||||
* @param conditionDetails A text block detailing the current condition (if it is needed).
|
||||
* @param date The date/time of the action. Will be set to the current date/time if not provided.
|
||||
*/
|
||||
'assets.assign'(assetId, assigneeType, assigneeId, condition, conditionDetails, date) {
|
||||
check(assigneeId, String);
|
||||
check(assigneeType, String);
|
||||
check(assetId, String);
|
||||
check(condition, String);
|
||||
if(conditionDetails) check(conditionDetails, String);
|
||||
if(date) check(date, Date);
|
||||
|
||||
if(!date) date = new Date();
|
||||
|
||||
if(condition !== 'New' && condition !== 'Like New' && condition !== 'Good' && condition !== 'Okay' && condition !== 'Damaged') {
|
||||
//Should never happen.
|
||||
console.error("Invalid condition option in assets.unassign(..)");
|
||||
throw new Meteor.Error("Invalid condition option.");
|
||||
}
|
||||
|
||||
if(assigneeType !== 'Student' && assigneeType !== 'Staff') {
|
||||
// Should never happen.
|
||||
console.error("Error: Received incorrect assignee type in adding an assignment.");
|
||||
console.error(assigneeType);
|
||||
throw new Meteor.Error("Error: Received incorrect assignee type in adding an assignment.");
|
||||
}
|
||||
else if(Roles.userIsInRole(Meteor.userId(), "laptop-management", {anyScope:true})) {
|
||||
let asset = Assets.findOne({assetId});
|
||||
|
||||
if(asset) {
|
||||
if(asset.assigneeId) {
|
||||
//TODO: Should we unassign and re-assign????
|
||||
console.error("Asset is already assigned! " + assetId);
|
||||
throw new Meteor.Error("Asset is already assigned.", "Cannot assign an asset that has already been assigned.");
|
||||
}
|
||||
else {
|
||||
Assets.update({assetId}, {$set: {assigneeType, assigneeId, assignmentDate: date, condition, conditionDetails}});
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error("Could not find the asset: " + assetId)
|
||||
}
|
||||
}
|
||||
else throw new Meteor.Error("User Permission Error");
|
||||
},
|
||||
/**
|
||||
* Removes an assignment for the asset.
|
||||
* TODO: Should create a historical record.
|
||||
* @param assetId The Asset ID (eg: 'Z1Q') of the asset (asset.assetId).
|
||||
* @param comment A textual comment on the reason for unassigning the asset. Should not contain condition information.
|
||||
* @param condition One of the condition options: [New, Like New, Good, Okay, Damaged]. 'Like New' is defined as very minor cosmetic damage. 'Good' is defined as some cosmetic damage or very minor screen damage. 'Okay' is defined as significant cosmetic damage, or screen/keyboard/trackpad damage but is still useable. 'Damaged' indicates significant damage and the device should not be reissued until it is repaired.
|
||||
* @param conditionDetails A text block detailing the current condition (if it is needed).
|
||||
* @param date The date/time of the action. Will be set to the current date/time if not provided.
|
||||
*/
|
||||
'assets.unassign'(assetId, comment, condition, conditionDetails, date) {
|
||||
check(assetId, String);
|
||||
if(date) check(date, Date);
|
||||
if(comment) check(comment, String);
|
||||
check(condition, String);
|
||||
if(conditionDetails) check(conditionDetails, String);
|
||||
|
||||
if(!date) date = new Date();
|
||||
|
||||
if(condition !== 'New' && condition !== 'Like New' && condition !== 'Good' && condition !== 'Okay' && condition !== 'Damaged') {
|
||||
//Should never happen.
|
||||
console.error("Invalid condition option in assets.unassign(..)");
|
||||
throw new Meteor.Error("Invalid condition option.");
|
||||
}
|
||||
|
||||
if(Roles.userIsInRole(Meteor.userId(), "laptop-management", {anyScope:true})) {
|
||||
let asset = Assets.findOne({assetId});
|
||||
|
||||
if(asset) {
|
||||
let assetType = AssetTypes.findOne({_id: asset.assetTypeId});
|
||||
|
||||
try {
|
||||
AssetAssignmentHistory.insert({assetKey: asset._id, assetId, serial: asset.serial, assetTypeName: (assetType ? assetType.name : "UNK"), assigneeType: asset.assigneeType, assigneeId: asset.assigneeId, startDate: asset.assignmentDate, endDate: date, startCondition: asset.condition, endCondition: condition, startConditionDetails: asset.conditionDetails, endConditionDetails: conditionDetails, comment});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
Assets.update({assetId}, {$unset: {assigneeType: "", assigneeId: "", assignmentDate: ""}, $set: {condition, conditionDetails}});
|
||||
}
|
||||
else {
|
||||
console.error("Could not find the asset: " + assetId);
|
||||
throw new Meteor.Error("Could not find the asset: " + assetId);
|
||||
}
|
||||
}
|
||||
else throw new Meteor.Error("User Permission Error");
|
||||
},
|
||||
/**
|
||||
* A fix to remove the AssetAssignment collection and merge it with the Asset collection.
|
||||
*/
|
||||
'assets.fixAssetAssignments'() {
|
||||
// Removed this since it should no longer be relevant.
|
||||
|
||||
// let assignmentDate = new Date();
|
||||
// //This function just removes the need for the asset-assignments collection and merges it with assets.
|
||||
// if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
// let assets = Assets.find({}).fetch();
|
||||
// let assetAssignments = AssetAssignments.find({}).fetch();
|
||||
//
|
||||
// let assetMap = assets.reduce((map, obj) => {
|
||||
// map[obj.assetId] = obj;
|
||||
// return map;
|
||||
// }, {});
|
||||
//
|
||||
// console.log(assetMap);
|
||||
// console.log("");
|
||||
//
|
||||
// for(let next of assetAssignments) {
|
||||
// console.log(next);
|
||||
// let asset = assetMap[next.assetId];
|
||||
// console.log("Updating " + asset.assetId + " to be assigned to " + next.assigneeType + ": " + next.assigneeId);
|
||||
// let c = Assets.update({assetId: asset.assetId}, {$set: {assigneeType: next.assigneeType, assigneeId: next.assigneeId, assignmentDate}});
|
||||
// console.log("Updated " + c + " Assets");
|
||||
// console.log(Assets.findOne({assetId: asset.assetId}));
|
||||
// }
|
||||
// }
|
||||
},
|
||||
/**
|
||||
* A fix to remove the AssetAssignment collection and merge it with the Asset collection.
|
||||
*/
|
||||
'assets.fixAssetCondition'() {
|
||||
// Removed this since it should no longer be relevant.
|
||||
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
Assets.update({assetTypeId: 'xPu8YK39pmQW93Fuz', condition: {$exists: false}}, {$set: {condition: 'Okay', conditionDetails: 'Automated Condition'}}, {multi: true}); //Lenovo E100 CB
|
||||
Assets.update({assetTypeId: 'casMp4pJ9t8FtpyuR', condition: {$exists: false}}, {$set: {condition: 'Good', conditionDetails: 'Automated Condition'}}, {multi: true}); //Lenovo E100 Charger
|
||||
Assets.update({assetTypeId: 'ZD9XiHqGr6TcKH9Nv', condition: {$exists: false}}, {$set: {condition: 'New'}}, {multi: true}); //Acer CB315 CB
|
||||
Assets.update({assetTypeId: 'mfE9NtiFBotb8kp4v', condition: {$exists: false}}, {$set: {condition: 'New'}}, {multi: true}); //Acer CB315 Charger
|
||||
Assets.update({assetTypeId: 'btEsKYxW4Sgf7T8nA', condition: {$exists: false}}, {$set: {condition: 'Good',conditionDetails: 'Automated Condition'}}, {multi: true}); //Dell 3100 Charger
|
||||
Assets.update({assetTypeId: '9bszeFJNPteMDbye5', condition: {$exists: false}}, {$set: {condition: 'Like New',conditionDetails: 'Automated Condition'}}, {multi: true}); //HP 11A CB
|
||||
Assets.update({assetTypeId: 'tCj7s5T2YcFXZEaqE', condition: {$exists: false}}, {$set: {condition: 'Like New',conditionDetails: 'Automated Condition'}}, {multi: true}); //HP 11A Charger
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// console.log("Assets setup.")
|
||||
149
imports/api/data-collection.js
Normal file
149
imports/api/data-collection.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import { check } from 'meteor/check';
|
||||
import { MongoClient } from 'mongodb';
|
||||
import {Assets} from "/imports/api/assets";
|
||||
import {Students} from "/imports/api/students";
|
||||
import {Staff} from "/imports/api/staff";
|
||||
import {AssetTypes} from "/imports/api/asset-types";
|
||||
//import {Roles} from 'alanning/roles';
|
||||
|
||||
// console.log("Setting Up Data Collection...")
|
||||
|
||||
//export const Records = new Mongo.Collection('records');
|
||||
let client;
|
||||
let database;
|
||||
let dataCollection;
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// let uri = process.env.MONGO_URL2;
|
||||
// uri = "mongodb://localhost:27017";
|
||||
//
|
||||
// //client = new MongoClient(uri);
|
||||
// MongoClient.connect(uri, (err, c) => {
|
||||
// client = c;
|
||||
// database = client.db("avusd-data-collection");
|
||||
// dataCollection = database.collection("records");
|
||||
// dataCollection.find({deviceId: "1e3e99ef-adf4-4aa2-8784-205bc60f0ce3"}).toArray((e, a) => {
|
||||
// if(e) console.log(e);
|
||||
// else console.log("Found " + a.length + " records.");
|
||||
// })
|
||||
// });
|
||||
|
||||
|
||||
// let results = Meteor.Records.find({deviceId: "1e3e99ef-adf4-4aa2-8784-205bc60f0ce3"}).fetch();
|
||||
// console.log(results);
|
||||
}
|
||||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.methods({
|
||||
/**
|
||||
* Collects Chromebook history given one of the possible parameters.
|
||||
* @param params An object with a single attribute. The attribute must be one of: deviceId, serial, email. It will find all Chromebook data that starts with the given attribute value.
|
||||
* @returns {any} Array of Chromebook data objects.
|
||||
*/
|
||||
'DataCollection.chromebookData'(params) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "laptop-management", {anyScope:true})) {
|
||||
let query = {};
|
||||
|
||||
// For asset ID's, we need to get the serial from the asset collection first.
|
||||
if(params.assetId) {
|
||||
let asset = Assets.findOne({assetId : params.assetId});
|
||||
|
||||
if(asset) {
|
||||
params.serial = asset.serial;
|
||||
params.regex = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (params.deviceId) query.deviceId = params.regex ? {
|
||||
$regex: params.deviceId,
|
||||
$options: "i"
|
||||
} : params.deviceId;
|
||||
else if (params.serial) query.serial = params.regex ? {
|
||||
$regex: params.serial,
|
||||
$options: "i"
|
||||
} : params.serial;
|
||||
// else if (params.assetId) {
|
||||
// let asset = Assets.findOne({assetId: params.assetId});
|
||||
//
|
||||
// if(asset.serial) {
|
||||
// // An exact search.
|
||||
// query.serial = asset.serial;
|
||||
// }
|
||||
// }
|
||||
else if (params.email) query.email = params.regex ? {
|
||||
$regex: params.email,
|
||||
$options: "i"
|
||||
} : params.email;
|
||||
else if (params.date) { //Assume that date is a number. Finds all Chromebook Data with the last check in time greater than or equal to the given date.
|
||||
query.endTime = {'$gte': params.date}
|
||||
}
|
||||
else {
|
||||
query = undefined;
|
||||
}
|
||||
|
||||
if(query) {
|
||||
// console.log("Collecting Chromebook Data: ");
|
||||
// console.log(query);
|
||||
|
||||
//Sort by the last time the record was updated from most to least recent.
|
||||
let result = Meteor.Records.find(query, {sort: {endTime: -1}}).fetch();
|
||||
// console.log("Found: ");
|
||||
// console.log(result);
|
||||
|
||||
//Add some additional data to the records.
|
||||
for (let next of result) {
|
||||
if (next.serial) {
|
||||
next.asset = Assets.findOne({serial: next.serial});
|
||||
}
|
||||
|
||||
if (next.email) {
|
||||
next.person = Students.findOne({email: next.email});
|
||||
|
||||
if (!next.person) next.person = Staff.findOne({email: next.email});
|
||||
}
|
||||
|
||||
if (next.asset) {
|
||||
next.assetType = AssetTypes.findOne({_id: next.asset.assetType})
|
||||
|
||||
if (next.asset.assigneeId) {
|
||||
next.assignedTo = next.asset.assigneeType === "Student" ? Students.findOne({_id: next.asset.assigneeId}) : Staff.findOne({_id: next.asset.assigneeId})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} else return null;
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
// 'tasks.setChecked'(taskId, setChecked) {
|
||||
// check(taskId, String);
|
||||
// check(setChecked, Boolean);
|
||||
//
|
||||
// const task = Tasks.findOne(taskId);
|
||||
// if (task.private && task.owner !== this.userId) {
|
||||
// // If the task is private, make sure only the owner can check it off
|
||||
// throw new Meteor.Error('not-authorized');
|
||||
// }
|
||||
//
|
||||
// Tasks.update(taskId, { $set: { checked: setChecked } });
|
||||
// },
|
||||
// 'tasks.setPrivate'(taskId, setToPrivate) {
|
||||
// check(taskId, String);
|
||||
// check(setToPrivate, Boolean);
|
||||
//
|
||||
// const task = Tasks.findOne(taskId);
|
||||
//
|
||||
// // Make sure only the task owner can make a task private
|
||||
// if (task.owner !== this.userId) {
|
||||
// throw new Meteor.Error('not-authorized');
|
||||
// }
|
||||
//
|
||||
// Tasks.update(taskId, { $set: { private: setToPrivate } });
|
||||
// },
|
||||
});
|
||||
}
|
||||
|
||||
// console.log("Data Collection setup.")
|
||||
93
imports/api/example-schema.js
Normal file
93
imports/api/example-schema.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import {AssetTypes} from "./asset-types";
|
||||
import {SimpleSchema} from "simpl-schema/dist/SimpleSchema";
|
||||
|
||||
const AssetTypesSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
label: "Name",
|
||||
optional: false,
|
||||
trim: true,
|
||||
index: 1,
|
||||
unique: true
|
||||
},
|
||||
tags: { //An array of ProductTag names. Note that we are not using the ProductTag ID's because I want a looser connection (if a ProductTag is deleted, it isn't a big deal if it isn't maintained in the Product records).
|
||||
type: [String],
|
||||
label: "Tags",
|
||||
optional: false,
|
||||
defaultValue: []
|
||||
},
|
||||
measures: { //A JSON array of Measure ID's.
|
||||
type: Array,
|
||||
label: "Measures",
|
||||
optional: false,
|
||||
defaultValue: []
|
||||
},
|
||||
'measures.$': {
|
||||
type: String,
|
||||
label: "Measure ID",
|
||||
regEx: SimpleSchema.RegEx.Id
|
||||
},
|
||||
aliases: { //A JSON array of alternate names.
|
||||
type: Array,
|
||||
label: "Aliases",
|
||||
optional: false,
|
||||
defaultValue: []
|
||||
},
|
||||
'aliases.$': {
|
||||
type: String
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
label: "Created On",
|
||||
optional: false
|
||||
},
|
||||
updatedAt: {
|
||||
type: Date,
|
||||
label: "Updated On",
|
||||
optional: true
|
||||
},
|
||||
deactivated: { //This is turned on first, if true it will hide the product in production views, but keep the product available in the sale views. It is intended to be turned on for products that are no longer produced, but for which we have remaining inventory.
|
||||
type: Boolean,
|
||||
label: "Deactivated",
|
||||
optional: true
|
||||
},
|
||||
hidden: { //Deactivated must first be true. Hides the product everywhere in the system except in historical pages. The inventory should be all sold prior to hiding a product.
|
||||
type: Boolean,
|
||||
label: "Hidden",
|
||||
optional: true
|
||||
},
|
||||
timestamp: {
|
||||
type: Date,
|
||||
label: "Timestamp",
|
||||
optional: true
|
||||
},
|
||||
weekOfYear: {
|
||||
type: Number,
|
||||
label: "Week Of Year",
|
||||
optional: true
|
||||
},
|
||||
amount: {
|
||||
type: Number,
|
||||
label: "Amount",
|
||||
optional: false,
|
||||
decimal: true
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
label: "Price",
|
||||
optional: false,
|
||||
min: 0,
|
||||
exclusiveMin: true,
|
||||
},
|
||||
assigneeType: {
|
||||
type: SimpleSchema.Integer,
|
||||
label: "Assignee Type",
|
||||
optional: false,
|
||||
min: 1,
|
||||
max: 2,
|
||||
exclusiveMin: false,
|
||||
exclusiveMax: false,
|
||||
},
|
||||
});
|
||||
|
||||
AssetTypes.attachSchema(AssetTypesSchema);
|
||||
11
imports/api/index.js
Normal file
11
imports/api/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import "./users.js";
|
||||
import "./data-collection.js";
|
||||
import "./admin.js";
|
||||
import "./students.js";
|
||||
import "./staff.js";
|
||||
import "./sites.js";
|
||||
import "./asset-types.js";
|
||||
import "./assets.js";
|
||||
import "./asset-assignment-history.js";
|
||||
|
||||
// console.log("Finished setting up server side models.");
|
||||
40
imports/api/records.js
Normal file
40
imports/api/records.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export const Records = new Mongo.Collection('records');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// This code only runs on the server
|
||||
Meteor.publish('records', function() {
|
||||
return Records.find({});
|
||||
});
|
||||
}
|
||||
|
||||
Meteor.methods({
|
||||
// 'tasks.setChecked'(taskId, setChecked) {
|
||||
// check(taskId, String);
|
||||
// check(setChecked, Boolean);
|
||||
//
|
||||
// const task = Tasks.findOne(taskId);
|
||||
// if (task.private && task.owner !== this.userId) {
|
||||
// // If the task is private, make sure only the owner can check it off
|
||||
// throw new Meteor.Error('not-authorized');
|
||||
// }
|
||||
//
|
||||
// Tasks.update(taskId, { $set: { checked: setChecked } });
|
||||
// },
|
||||
// 'tasks.setPrivate'(taskId, setToPrivate) {
|
||||
// check(taskId, String);
|
||||
// check(setToPrivate, Boolean);
|
||||
//
|
||||
// const task = Tasks.findOne(taskId);
|
||||
//
|
||||
// // Make sure only the task owner can make a task private
|
||||
// if (task.owner !== this.userId) {
|
||||
// throw new Meteor.Error('not-authorized');
|
||||
// }
|
||||
//
|
||||
// Tasks.update(taskId, { $set: { private: setToPrivate } });
|
||||
// },
|
||||
});
|
||||
38
imports/api/sites.js
Normal file
38
imports/api/sites.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import {Mongo} from "meteor/mongo";
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import {Students} from "./students";
|
||||
import {Staff} from "./staff";
|
||||
import { Roles } from 'meteor/alanning:roles';
|
||||
|
||||
export const Sites = new Mongo.Collection('sites');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// This code only runs on the server
|
||||
Meteor.publish('sites', function() {
|
||||
return Sites.find({});
|
||||
});
|
||||
}
|
||||
Meteor.methods({
|
||||
'sites.update'(_id, name) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
Sites.update({_id}, {$set: {name}});
|
||||
}
|
||||
},
|
||||
'sites.add'(name) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
Sites.insert({name});
|
||||
}
|
||||
},
|
||||
'sites.remove'(_id) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
let site = Sites.find({_id});
|
||||
|
||||
if(site) {
|
||||
//Clear any site references in student/room entries.
|
||||
Students.update({siteId: _id}, {$unset: {siteId: 1}});
|
||||
Staff.update({siteId: _id}, {$unset: {siteId: 1}});
|
||||
Sites.remove({_id});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
161
imports/api/staff.js
Normal file
161
imports/api/staff.js
Normal file
@@ -0,0 +1,161 @@
|
||||
import {Mongo} from "meteor/mongo";
|
||||
import {Meteor} from "meteor/meteor";
|
||||
import { Roles } from 'meteor/alanning:roles';
|
||||
import {check} from "meteor/check";
|
||||
import {Sites} from "/imports/api/sites";
|
||||
import {parse} from "csv-parse";
|
||||
|
||||
// console.log("Setting Up Staff...")
|
||||
|
||||
export const Staff = new Mongo.Collection('staff');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// This code only runs on the server
|
||||
Meteor.publish('staff', function(siteId) {
|
||||
if(siteId) check(siteId, String);
|
||||
return siteId ? Staff.find({siteId}) : Staff.find({});
|
||||
});
|
||||
}
|
||||
Meteor.methods({
|
||||
'staff.add'(id, firstName, lastName, email, siteId) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
Staff.insert({id, firstName, lastName, email, siteId});
|
||||
}
|
||||
},
|
||||
'staff.update'(_id, id, firstName, lastName, email, siteId) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
Staff.update({_id}, {$set: {id, firstName, lastName, email, siteId}});
|
||||
}
|
||||
},
|
||||
'staff.remove'(_id) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
//TODO: Need to first verify there are no checked out assets to the staff member.
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Assumes that the ID field is a unique ID that never changes for staff.
|
||||
* This must be true in order for duplicate staff to be avoided.
|
||||
* Will automatically update staff data, including the site he/she is associated with.
|
||||
*
|
||||
* Expects the CSV string to contain comma delimited data in the form:
|
||||
* ID, email, first name, last name
|
||||
*
|
||||
* The query in Aeries is: `LIST STF ID FN LN EM PSC`.
|
||||
* A more complete Aeries query: `LIST STF ID FN LN EM BY FN IF PSC = 5`
|
||||
* Note that you will want to run this query for each school, and the district. The example above sets the school to #5 (PSC).
|
||||
* Run the query in Aeries as a `Report`, select TXT, and upload here.
|
||||
*
|
||||
* Aeries adds a header per 'page' of data (I think 35 entries per page).
|
||||
* Example:
|
||||
* Anderson Valley Jr/Sr High School,6/11/2022
|
||||
* 2021-2022,Page 1
|
||||
* StuEmail,Student ID,First Name,Last Name,Grade,First Name Alias,Last Name Alias
|
||||
* @type: Currently only supports 'CSV' or 'Aeries Text Report'
|
||||
*/
|
||||
'staff.loadCsv'(csv, type, siteId) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
check(csv, String);
|
||||
check(siteId, String);
|
||||
|
||||
let site = Sites.findOne({_id: siteId});
|
||||
|
||||
if(site) {
|
||||
let cleanCsv;
|
||||
let lines = csv.split(/\r?\n/);
|
||||
let pageHeader = type === 'Aeries Text Report' ? lines[0] : null; // Skip the repeating header lines for an Aeries text report.
|
||||
let skip = type === 'CSV' ? 1 : 0; // Skip the first line of a CSV file (headers).
|
||||
|
||||
// Remove headers from the CSV.
|
||||
for(const line of lines) {
|
||||
if (skip > 0) skip--;
|
||||
else if (pageHeader && line === pageHeader) {
|
||||
skip = 2;
|
||||
} else {
|
||||
if(!cleanCsv) cleanCsv = "";
|
||||
else cleanCsv += '\r\n';
|
||||
cleanCsv += line;
|
||||
}
|
||||
}
|
||||
|
||||
//console.log(cleanCsv);
|
||||
|
||||
// Note: This doesn't work because some values are quoted and contain commas as a value character.
|
||||
// Parse the CSV (now without any headers).
|
||||
// lines = cleanCsv.split(/\r\n/);
|
||||
// for(const line of lines) {
|
||||
// let values = line.split(/\s*,\s*/);
|
||||
//
|
||||
// if(values.length >= 5) {
|
||||
// let id = values[0];
|
||||
// let email = values[1];
|
||||
// let firstName = values[2];
|
||||
// let lastName = values[3];
|
||||
// let grade = parseInt(values[4], 10);
|
||||
// let firstNameAlias = "";
|
||||
// let active = true;
|
||||
//
|
||||
// if(values.length > 5) firstNameAlias = values[5];
|
||||
//
|
||||
// let student = {siteId, email, id, firstName, lastName, grade, firstNameAlias, active};
|
||||
//
|
||||
// // console.log(student);
|
||||
// // Update or insert in the db.
|
||||
// console.log("Upserting: " + student);
|
||||
// Students.upsert({id}, {$set: student});
|
||||
// }
|
||||
// }
|
||||
|
||||
const bound = Meteor.bindEnvironment((callback) => {callback();});
|
||||
|
||||
parse(cleanCsv, {}, function(err, records) {
|
||||
bound(() => {
|
||||
if(err) console.error(err);
|
||||
else {
|
||||
let foundIds = new Set();
|
||||
let duplicates = [];
|
||||
console.log("Found " + records.length + " records.");
|
||||
|
||||
for(const values of records) {
|
||||
let id = values[0];
|
||||
let email = values[1];
|
||||
let firstName = values[2];
|
||||
let lastName = values[3];
|
||||
let grade = parseInt(values[4], 10);
|
||||
let firstNameAlias = "";
|
||||
let active = true;
|
||||
|
||||
if(values.length > 5) firstNameAlias = values[5];
|
||||
|
||||
let staff = {siteId, email, id, firstName, lastName, active};
|
||||
|
||||
// Track the staff ID's and record duplicates. This is used to ensure our counts are accurate later.
|
||||
if(foundIds.has(staff.id)) {
|
||||
duplicates.push(staff.id);
|
||||
}
|
||||
else {
|
||||
foundIds.add(staff.id);
|
||||
}
|
||||
|
||||
//console.log(staff);
|
||||
try {
|
||||
Staff.upsert({id: staff.id}, {$set: staff});
|
||||
}
|
||||
catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(duplicates.length + " records were duplicates:");
|
||||
console.log(duplicates);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
else {
|
||||
console.log("Failed to find the site with the ID: " + siteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// console.log("Staff setup.")
|
||||
150
imports/api/students.js
Normal file
150
imports/api/students.js
Normal file
@@ -0,0 +1,150 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import { check } from 'meteor/check';
|
||||
import {Sites} from "./sites";
|
||||
import { Roles } from 'meteor/alanning:roles';
|
||||
import {parse} from 'csv-parse';
|
||||
|
||||
export const Students = new Mongo.Collection('students');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
Students.createIndex({id: 1}, {name: "External ID", unique: true});
|
||||
|
||||
// This code only runs on the server
|
||||
Meteor.publish('students', function(siteId) {
|
||||
if(siteId) check(siteId, String);
|
||||
return siteId ? Students.find({siteId}) : Students.find({});
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
'students.add'(id, firstName, lastName, email, siteId, grade) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
Students.insert({id, firstName, lastName, email, siteId, grade});
|
||||
}
|
||||
},
|
||||
'students.update'(_id, id, firstName, lastName, email, siteId, grade) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
Students.update({_id}, {$set: {id, firstName, lastName, email, siteId, grade}});
|
||||
}
|
||||
},
|
||||
'students.remove'(_id) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
//TODO: Need to first verify there are no checked out assets to the staff member.
|
||||
}
|
||||
},
|
||||
'students.getPossibleGrades'() {
|
||||
return Students.rawCollection().distinct('grade', {});
|
||||
},
|
||||
/**
|
||||
* Sets a first name alias that can be overridden by the one that is imported.
|
||||
* @param _id The student's database ID.
|
||||
* @param alias The alias to set for the student.
|
||||
*/
|
||||
'students.setAlias'(_id, alias) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
check(_id, String);
|
||||
check(alias, String);
|
||||
|
||||
Students.update({_id}, !alias || !alias.length() ? {$unset: {alias: true}} : {$set: {alias}});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Assumes that the ID field is a unique ID that never changes for a student.
|
||||
* This must be true in order for duplicate students to be avoided.
|
||||
* Will automatically update a student's data, including the site he/she is associated with.
|
||||
*
|
||||
* Expects the CSV string to contain comma delimited data in the form:
|
||||
* email, student ID, first name, last name, grade, first name alias, last name alias
|
||||
*
|
||||
* The query in Aeries is: `LIST STU ID SEM FN LN NG FNA`.
|
||||
* A more complete Aeries query: `LIST STU STU.ID STU.SEM STU.FN STU.LN STU.NG BY STU.NG STU.SEM IF STU.NG >= 7 AND NG <= 12 AND STU.NS = 5`
|
||||
* Note that FNA (First Name Alias) is optional.
|
||||
* Note that you might want to include a school ID in the IF if you have multiple schools in the district.
|
||||
* The query in SQL is: `SELECT [STU].[ID] AS [Student ID], [STU].[SEM] AS [StuEmail], STU.FN AS [First Name], STU.LN AS [Last Name], [STU].[GR] AS [Grade], [STU].[FNA] AS [First Name Alias], [STU].[LNA] AS [Last Name Alias] FROM (SELECT [STU].* FROM STU WHERE [STU].DEL = 0) STU WHERE ( [STU].SC = 5) ORDER BY [STU].[LN], [STU].[FN];`.
|
||||
* Run the query in Aeries as a `Report`, select TXT, and upload here.
|
||||
*
|
||||
* Aeries adds a header per 'page' of data (I think 35 entries per page).
|
||||
* Example:
|
||||
* Anderson Valley Jr/Sr High School,6/11/2022
|
||||
* 2021-2022,Page 1
|
||||
* Student ID, Email, First Name,Last Name,Grade,(opt) First Name Alias
|
||||
* @type: Currently only supports 'CSV' or 'Aeries Text Report'
|
||||
*/
|
||||
'students.loadCsv'(csv, type, siteId) {
|
||||
if(Roles.userIsInRole(Meteor.userId(), "admin", {anyScope:true})) {
|
||||
check(csv, String);
|
||||
check(siteId, String);
|
||||
|
||||
let site = Sites.findOne({_id: siteId});
|
||||
|
||||
if(site) {
|
||||
let cleanCsv;
|
||||
let lines = csv.split(/\r?\n/);
|
||||
let pageHeader = type === 'Aeries Text Report' ? lines[0] : null; // Skip the repeating header lines for an Aeries text report.
|
||||
let skip = type === 'CSV' ? 1 : 0; // Skip the first line of a CSV file (headers).
|
||||
|
||||
// Remove headers from the CSV.
|
||||
for(const line of lines) {
|
||||
if (skip > 0) skip--;
|
||||
else if (pageHeader && line === pageHeader) {
|
||||
skip = 2;
|
||||
} else {
|
||||
if(!cleanCsv) cleanCsv = "";
|
||||
else cleanCsv += '\r\n';
|
||||
cleanCsv += line;
|
||||
}
|
||||
}
|
||||
|
||||
const bound = Meteor.bindEnvironment((callback) => {callback();});
|
||||
|
||||
parse(cleanCsv, {}, function(err, records) {
|
||||
bound(() => {
|
||||
if(err) console.error(err);
|
||||
else {
|
||||
let foundIds = new Set();
|
||||
let duplicates = [];
|
||||
console.log("Found " + records.length + " records.");
|
||||
|
||||
for(const values of records) {
|
||||
let id = values[0];
|
||||
let email = values[1];
|
||||
let firstName = values[2];
|
||||
let lastName = values[3];
|
||||
let grade = parseInt(values[4], 10);
|
||||
let firstNameAlias = "";
|
||||
let active = true;
|
||||
|
||||
if(values.length > 5) firstNameAlias = values[5];
|
||||
|
||||
let student = {siteId, email, id, firstName, lastName, grade, firstNameAlias, active};
|
||||
|
||||
// Track the student ID's and record duplicates. This is used to ensure our counts are accurate later.
|
||||
if(foundIds.has(student.id)) {
|
||||
duplicates.push(student.id);
|
||||
}
|
||||
else {
|
||||
foundIds.add(student.id);
|
||||
}
|
||||
|
||||
try {
|
||||
Students.upsert({id: student.id}, {$set: student});
|
||||
}
|
||||
catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(duplicates.length + " records were duplicates:");
|
||||
console.log(duplicates);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
else {
|
||||
console.log("Failed to find the site with the ID: " + siteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user