Skip to content

Commit

Permalink
Transfer old data (#29)
Browse files Browse the repository at this point in the history
* Create basic endpoint skeleton

* get endpoint 500 error

* Fix routing

* Get member endpoint to work

* Return more fields from endpoint

* Return object id from endpoint

* Add filterSensitiveInfo util

* Give all non-required Member fields a default of null

* Move passport-setup to utils

* Remove console.log

* Add members endpoints

* Fix filterSensitiveInfo util

* Use passport-setup.js in utils

* Use auth endpoint

* Move /api/auth/user to /api/members/current

* Add omit fields to GET /:memberId

* Change current user endpoint in frontend

* Add more auth utils

* Use isDirector from module in members routes

* Change level default in model back to TBD

* Add level priority explanation

* Add detailed permission checking in members endpoint

* Fix difference function

* Fix typos in user-utils

* Add _id as neverEditable field

* Fix allFields not being checked correctly

* Filter viewable fields when returning from PUT

* Pull enum options from backend

* Add status options to endpoint

* Disable dropdowns

* Add labels to fields

* Create wrapper for getting member by ID

* Add boolean selector

* Add number fields

* Add fields for basic string attributes

* Retrieve schema type from DB

* Move preprocessing to backend

* Fix enum type detection

* Remove duplicated code

* Fix attribute change error

* Run formatter

* Remove warnings

* Format client

* Populate edit fields from DB

* Add types to props

* Run formatter

* Remove alert

* Run formatter

* Get permissions from backend

* Disable input boxes if missing permissions

* Add enum dropdowns

* Format client

* Remove user ID override

* Change to ES6 defaults

* Remove unused package

* Change var to let

* Concisen member options endpoint

* Delete env file

* Unexport schema

* Use concise property iteration

* Make /options endpoint concise

* Remove unneeded exports

* Capitalize constant

* Shorten attribute change callback

* Run formatter

* Turn off default-props in linter

* Fix var name typo

* Change member page routing

* Run formatter

* Use useParams hook

* Change object syntax

* Run formatter

* Start creating tool to transfer data

* Consolidate envs and clean up app.js

* Fix yarn test not using .env

* Add lint-staged pre-commit hook (#27)

* Add lint-staged pre-commit hook

* Format "unstaged" files

* Sync prettier versions

* Format all files using new Prettier config

* Finish writing tool

* Format remaining fields

* Check for member uniqueness

* Fix enum formatting

* Run formatter

* Run formatter

* Run format with new hook

* Remove duplicate of Profile.js

Co-authored-by: ishaansharma <[email protected]>
Co-authored-by: Jeffrey Tang <[email protected]>
Co-authored-by: Yousef Ahmed <[email protected]>
  • Loading branch information
4 people authored and n3a9 committed May 23, 2021
1 parent 7b944c0 commit c4e71fa
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all"
}
1 change: 1 addition & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
},
"dependencies": {
"cors": "^2.8.5",
"csvtojson": "^2.0.10",
"debug": "~2.6.9",
"dotenv": "^8.2.0",
"express": "~4.16.1",
Expand Down
8 changes: 4 additions & 4 deletions api/src/models/member.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const levelEnum = {

const locationEnum = {
REMOTE: 'REMOTE',
CAMPUS: 'CAMPUS',
CAMPUS: 'ON_CAMPUS',
TBD: 'TBD',
};

Expand All @@ -23,10 +23,10 @@ const roleEnum = {
EXTERNAL_DIRECTOR: 'EXTERNAL_DIRECTOR',
ACADEMY_LEAD: 'ACADEMY_LEAD',
TECH_LEAD: 'TECH_LEAD',
PR_LEAD: 'PR_LEAD',
PR_LEAD: 'PRODUCT_RESEARCH_LEAD',
PRODUCT_MANAGER: 'PRODUCT_MANAGER',
PRODUCT_DESIGNER: 'PRODUCT_DESIGNER',
DEVELOPER: 'DEVELOPER',
DEVELOPER: 'SOFTWARE_DEVELOPER',
ACADEMY_MEMBER: 'ACADEMY_MEMBER',
TBD: 'TBD',
};
Expand Down Expand Up @@ -58,7 +58,7 @@ const classStandingEnum = {
const Member = new mongoose.Schema({
firstName: { type: String, default: null },
lastName: { type: String, default: null },
oauthID: { type: String, default: null, unique: true },
oauthID: { type: String, unique: true, sparse: true },
email: { type: String, default: null, unique: true },
phone: { type: String, default: null },
netID: { type: String, default: null },
Expand Down
195 changes: 195 additions & 0 deletions api/tools/db_population_tool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
const dotenv = require('dotenv');
const path = require('path');
dotenv.config({
path: path.resolve(__dirname, '../src/.env'),
});

const csv = require('csvtojson');
const Member = require('../src/models/member');
require('../src/utils/mongo-setup');

// Regex to capture each part the grad sem year column
// Ex: "Fall 2020 (Freshman)" captures "Fall", "2020", "Freshman"
const REGEX_GRAD_SEM_YEAR = /([A-Za-z]+)\s([0-9]+)\s+\(([A-Za-z\s+]+)\)/;
const REGEX_GENERATION_YEAR = /([A-Za-z]+)\s([0-9]+)/;

// Download the CSV file onto your machine and use abs path
const csvFilePath = 'C:\\Users\\mattw\\Downloads\\FA20Membership.csv';

// These are the columns that requie special processing. I.e. the data can't be directly copy and pasted
const SPECIAL_COLUMNS_ENUM = {
NAME: 'NAME',
GRAD_SEM_YEAR: 'GRAD_SEM_YEAR',
GENERATION: 'GENERATION',
BIRTHDATE: 'BIRTHDATE',
DUES: 'DUES',
};

// These are the columns that are an enum value. Formatting is done to ensure they meet the backend options.
const ENUM_COLUMNS_ENUM = {
LOCATION: 'location',
ROLE: 'role',
LEVEL: 'level',
STATUS: 'status',
};

// The names of the columns in order from left to right. Name each column according to it's DB field name except for special fields
const columns = [
SPECIAL_COLUMNS_ENUM.NAME,
'email',
'phone',
'netID',
'UIN',
'major',
SPECIAL_COLUMNS_ENUM.BIRTHDATE,
'github',
'snapchat',
'instagram',
SPECIAL_COLUMNS_ENUM.GRAD_SEM_YEAR,
SPECIAL_COLUMNS_ENUM.GENERATION,
ENUM_COLUMNS_ENUM.LOCATION,
ENUM_COLUMNS_ENUM.ROLE,
ENUM_COLUMNS_ENUM.LEVEL,
ENUM_COLUMNS_ENUM.STATUS,
SPECIAL_COLUMNS_ENUM.DUES,
];
const specialColumns = Object.values(SPECIAL_COLUMNS_ENUM);
const enumColumns = Object.values(ENUM_COLUMNS_ENUM);

async function main() {
// Data is array of JSON objects, where each key is field1, field2, ..., field19.
const sheetData = await csv({ noheader: true }).fromFile(csvFilePath);

// Remove the header row
sheetData.shift();
sheetData.forEach((memberJSON) => saveJSONIntoDB(memberJSON));
}

function saveJSONIntoDB(memberJSON) {
try {
const model = createModelFromJSON(memberJSON);
insertModelIntoDB(model);
} catch (error) {
console.log(
`Could not add member to database. \n Error: ${error} \n Data: ${memberJSON}`,
);
}
}

function createModelFromJSON(memberJSON) {
const memberObj = {};

for (let i = 0; i < columns.length; i++) {
const fieldLabel = `field${i + 1}`;
if (specialColumns.includes(columns[i])) {
processSpecialField(memberObj, columns[i], memberJSON[fieldLabel]);
} else if (enumColumns.includes(columns[i])) {
processEnumField(memberObj, columns[i], memberJSON[fieldLabel]);
} else {
processSimpleField(memberObj, columns[i], memberJSON[fieldLabel]);
}
}

return memberObj;
}

async function insertModelIntoDB(model) {
const duplicateUsers = await Member.find({ email: model.email });
if (duplicateUsers.length > 0)
console.log(`Member ${model.firstName} ${model.lastName} already in DB!`);
else Member.create(model);
}

function processSimpleField(memberObj, fieldName, fieldValue) {
memberObj[fieldName] = fieldValue;
}

function processSpecialField(memberObj, fieldName, fieldValue) {
switch (fieldName) {
case SPECIAL_COLUMNS_ENUM.NAME:
processName(memberObj, fieldValue);
break;
case SPECIAL_COLUMNS_ENUM.GRAD_SEM_YEAR:
prcoessGradSemYear(memberObj, fieldValue);
break;
case SPECIAL_COLUMNS_ENUM.GENERATION:
processGeneration(memberObj, fieldValue);
break;
case SPECIAL_COLUMNS_ENUM.BIRTHDATE:
processBirthdate(memberObj, fieldValue);
break;
case SPECIAL_COLUMNS_ENUM.DUES:
processDues(memberObj, fieldValue);
break;
}
}

/**
* The spreadsheet stores name as one field, yet the DB stores first and last. We need
* to split the name so it can be stored in the DB.
*/
function processName(memberObj, nameValue) {
const nameComponents = nameValue.split(' ');
if (nameComponents.length != 2)
throw `${nameValue}, has an unparsable name. They must be entered manually.`;

[memberObj['firstName'], memberObj['lastName']] = nameComponents;
}

/**
* This field contains class standing, grad year, and grad month
*/
function prcoessGradSemYear(memberObj, gradSemYearValue) {
let match = gradSemYearValue.match(REGEX_GRAD_SEM_YEAR);
if (!match || match.length != 4)
throw `Could not parse Grad Semester/Year column (${gradSemYearValue}).`;

match = match.slice(1, 4);
[
memberObj['gradSemester'],
memberObj['gradYear'],
memberObj['classStanding'],
] = formatEnumFields(match);
}

function processGeneration(memberObj, generationValue) {
let match = generationValue.match(REGEX_GENERATION_YEAR);
if (!match || match.length != 3)
throw `Could not parse Generation Year column (${generationValue}).`;

match = match.slice(1, 3);
[
memberObj['generationSemester'],
memberObj['generationYear'],
] = formatEnumFields(match);
}

function processBirthdate(memberObj, birthdateValue) {
memberObj['birthdate'] = Date.parse(birthdateValue);
}

function processEnumField(memberObj, fieldName, enumValue) {
memberObj[fieldName] = formatEnumField(enumValue);
}

/**
* Formats the enum fields as follows:
* - Replace spaces and dashes with underscores
* - Captialize entire string
*/
function formatEnumField(enumValue) {
let underscoredValue = enumValue.replace(/-|\s/g, '_');
return underscoredValue.toUpperCase();
}

function formatEnumFields(enumValues) {
return enumValues.map((val) => formatEnumField(val));
}

function processDues(memberObj, duesValue) {
memberObj['areDuesPaid'] = ['Y', 'YES', 'T', 'TRUE'].includes(
duesValue.toUpperCase(),
);
}

main();
28 changes: 27 additions & 1 deletion api/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==

bluebird@^3.5.1:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==

[email protected]:
version "1.18.3"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4"
Expand Down Expand Up @@ -1347,6 +1352,15 @@ cssstyle@^2.0.0:
dependencies:
cssom "~0.3.6"

csvtojson@^2.0.10:
version "2.0.10"
resolved "https://registry.yarnpkg.com/csvtojson/-/csvtojson-2.0.10.tgz#11e7242cc630da54efce7958a45f443210357574"
integrity sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==
dependencies:
bluebird "^3.5.1"
lodash "^4.17.3"
strip-bom "^2.0.0"

dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
Expand Down Expand Up @@ -2648,6 +2662,11 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0:
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=

is-utf8@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=

is-windows@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
Expand Down Expand Up @@ -3285,7 +3304,7 @@ lodash.sortby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=

lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.20:
lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.3:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
Expand Down Expand Up @@ -4757,6 +4776,13 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"

strip-bom@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=
dependencies:
is-utf8 "^0.2.0"

strip-bom@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';

import EnumAttribute from './EnumAttribute';

const BooleanAttribute = ({
Expand Down
Loading

0 comments on commit c4e71fa

Please sign in to comment.