Commit c5badbc5 authored by Romain Loth's avatar Romain Loth

Change DB schema 1 table => 3 tables + add missing cols (homepage, jobsearch,...

Change DB schema 1 table => 3 tables + add missing cols (homepage, jobsearch, position, affiliation_id)
parent 6ddedc31
...@@ -5,26 +5,80 @@ ...@@ -5,26 +5,80 @@
mysql -uroot -pvery-safe-pass -h $SQL_HOST -P 3306 mysql -uroot -pvery-safe-pass -h $SQL_HOST -P 3306
# --- after connection to mysql # --- after connection to mysql
CREATE DATABASE comex_shared ; CREATE DATABASE comex_shared CHARACTER SET utf8 COLLATE utf8_general_ci ;
USE comex_shared ; USE comex_shared ;
CREATE TABLE comex_registrations ( CREATE TABLE scholars (
doors_uid char(36) not null unique, doors_uid char(36) not null unique primary key,
last_modified_date char(24) not null, last_modified_date char(24) not null,
email varchar(255) not null unique primary key, email varchar(255) not null unique,
initials varchar(7) not null,
country varchar(60) not null, country varchar(60) not null,
first_name varchar(30) not null, first_name varchar(30) not null,
middle_name varchar(30), middle_name varchar(30),
last_name varchar(50) not null, last_name varchar(50) not null,
jobtitle varchar(30) not null, initials varchar(7) not null,
keywords varchar(350) not null, affiliation_id int(15) not null,
institution varchar(120) not null, position varchar(30), -- eg Director
institution_type varchar(50) not null, hon_title varchar(30), -- eg Doctor
team_lab varchar(50),
institution_city varchar(50),
interests_text varchar(1200), interests_text varchar(1200),
community_hashtags varchar(350), community_hashtags varchar(350),
gender char(1), gender char(1),
pic_file mediumblob job_looking_date char(24), -- null if not looking for a job
home_url varchar(120), -- homepage
pic_url varchar(120), -- an alternative to pic_file blob
pic_file mediumblob,
record_status varchar(10),
INDEX uid_index_sch (doors_uid),
INDEX country_index_sch (country),
INDEX affs_index_sch (affiliation_id)
) ; ) ;
-- affiliations: institutions and labs
CREATE TABLE affiliations(
affid int(15) not null auto_increment,
org varchar(120) not null,
org_type varchar(50) not null,
team_lab varchar(120),
org_city varchar(50),
INDEX affid_index_affs (affid),
PRIMARY KEY (affid),
-- we chose not to put org_type in unique key: should be entailed by other 3
-- TODO doesn't yet prevent entering the same info twice !!
UNIQUE KEY full_affiliation (org, team_lab, org_city)
);
ALTER TABLE scholars ADD FOREIGN KEY (affiliation_id) REFERENCES affiliations(affid) ;
-- keyword/subject terms
CREATE TABLE keywords(
kwid int(15) not null auto_increment,
kwstr char(50) not null unique, -- eg 'complex networks'
INDEX kwid_index_kws (kwid),
INDEX kwstr_index_kws (kwstr),
PRIMARY KEY (kwid)
);
-- relationship scholars <n=n> keywords
CREATE TABLE sch_kw(
uid char(36) not null,
kwid int(15) not null,
INDEX uid_index_schkw (uid),
INDEX kwid_index_schkw (kwid),
PRIMARY KEY (uid, kwid),
FOREIGN KEY (uid) REFERENCES scholars(doors_uid) ON DELETE CASCADE,
FOREIGN KEY (kwid) REFERENCES keywords(kwid)
);
-- linked identities (various users' ids on soc. media and research networks)
-- TODO use this :)
CREATE TABLE linked_ids(
linkid int(15) not null auto_increment,
uid char(36) not null,
ext_id_type char(50) not null, -- eg 'orcid','LinkedIn','Twitter'
ext_id char(50) not null, -- eg "0000-0002-1825-0097"
INDEX uid_index_eids (uid),
PRIMARY KEY (linkid),
FOREIGN KEY (uid) REFERENCES scholars(doors_uid) ON DELETE CASCADE
);
``` ```
This diff is collapsed.
...@@ -18,24 +18,33 @@ ...@@ -18,24 +18,33 @@
*/ */
// the target columns in DB: tuple (name, mandatoryBool, maxChars (or nChars)) // the target columns in DB: tuple (name, mandatoryBool, maxChars (or nChars))
var COLS = [ ["doors_uid", false, 36, 'exact'], var COLS = [ ["doors_uid", true, 36, 'exact'],
["last_modified_date", true, 24, 'exact'], ["last_modified_date", true, 24, 'exact'],
["email", true, 255], ["email", true, 255],
["initials", true, 7],
["country", true, 60], ["country", true, 60],
["first_name", true, 30], ["first_name", true, 30],
["middle_name", false, 30], ["middle_name", false, 30],
["last_name", true, 50], ["last_name", true, 50],
["jobtitle", true, 30], ["initials", true, 7],
["keywords", true, 350], ["position", false, 30],
["institution", true, 120], ["hon_title", false, 30],
["institution_type", true, 50],
["team_lab", false, 50],
["institution_city", false, 50],
["interests_text", false, 1200], ["interests_text", false, 1200],
["community_hashtags", false, 350], ["community_hashtags", false, 350],
["gender", false, 1, 'exact'], ["gender", false, 1, 'exact'],
["pic_file", false, null]] ["job_looking_date", false, 24, 'exact'],
["home_url", false, 120],
["pic_url", false, 120],
["pic_file", false, null],
// ==> *scholars* table
["keywords", true, 350],
// ==> *keywords* table
["org", true, 120],
["org_type", true, 50],
["team_lab", false, 120],
["org_city", false, 50]]
// ==> *affiliations* table
// vars that will be used during the interaction // vars that will be used during the interaction
// NB other vars defined in main scope but just before their respective funs // NB other vars defined in main scope but just before their respective funs
...@@ -47,6 +56,9 @@ var regTimestamp = document.getElementById('last_modified_date') ...@@ -47,6 +56,9 @@ var regTimestamp = document.getElementById('last_modified_date')
var uidInput = document.getElementById('doors_uid') var uidInput = document.getElementById('doors_uid')
var email = document.getElementById('email') var email = document.getElementById('email')
// dates up to 2049/12/31
var validDate = new RegExp( /^20[0-4][0-9]\/(?:0?[1-9]|1[0-2])\/(?:0?[1-9]|[1-2][0-9]|3[0-1])$/)
// str of the form: doors_hostname:doors_port // str of the form: doors_hostname:doors_port
var doorsConnectParam = document.getElementById('doors_connect').value var doorsConnectParam = document.getElementById('doors_connect').value
...@@ -58,7 +70,8 @@ var captchaCheck = document.getElementById('my-captchaHash') ...@@ -58,7 +70,8 @@ var captchaCheck = document.getElementById('my-captchaHash')
var subPage1Style = document.getElementById('subpage_1').style var subPage1Style = document.getElementById('subpage_1').style
var subPage2Style = document.getElementById('subpage_2').style var subPage2Style = document.getElementById('subpage_2').style
var teamCityDivStyle = document.getElementById('team_city_div').style var teamCityDivStyle = document.getElementById('team_city_div').style
var otherInstDivStyle = document.getElementById('other_institution_div').style var otherInstDivStyle = document.getElementById('other_org_div').style
var jobLookingDivStyle = document.getElementById('job_looking_div').style
// param for generation & validation // param for generation & validation
var realCaptchaLength = 5 var realCaptchaLength = 5
...@@ -78,6 +91,7 @@ var doorsMessage = document.getElementById('doors_ret_message') ...@@ -78,6 +91,7 @@ var doorsMessage = document.getElementById('doors_ret_message')
var passStatus = false var passStatus = false
var emailStatus = false var emailStatus = false
var captchaStatus = false var captchaStatus = false
var jobLookingDateStatus = false
submitButton.disabled = true submitButton.disabled = true
theForm.onkeyup = testAsYouGo theForm.onkeyup = testAsYouGo
theForm.onchange = testAsYouGo theForm.onchange = testAsYouGo
...@@ -97,11 +111,10 @@ function testAsYouGo() { ...@@ -97,11 +111,10 @@ function testAsYouGo() {
captchaStatus = (captcha.value.length == realCaptchaLength) captchaStatus = (captcha.value.length == realCaptchaLength)
// for debug
checkPassStatus() checkPassStatus()
checkJobDateStatus()
// TODO all other mandatory fields should be checked here if (passStatus && emailStatus && captchaStatus && jobLookingDateStatus) {
if (passStatus && emailStatus && captchaStatus) {
submitButton.disabled = false submitButton.disabled = false
} }
else { else {
...@@ -369,14 +382,18 @@ function validateAndMsg() { ...@@ -369,14 +382,18 @@ function validateAndMsg() {
submitButton.disabled = true submitButton.disabled = true
mainMessage.style.display = 'block' mainMessage.style.display = 'block'
// objectify the form
wholeFormData = new FormData(theForm);
var valid = true var valid = true
// TODO pass mainMessage ref as arg // TODO pass mainMessage ref as arg
mainMessage.innerHTML = "Validating the form..." mainMessage.innerHTML = "Validating the form..."
// also reformat the jobDate from "YYYY/MM/DD" to ISO
if (jobDate.value.length) jobDate.value = (new Date(jobDate.value)).toISOString()
// objectify the form
wholeFormData = new FormData(theForm);
var missingFields = [] var missingFields = []
var toolongFields = [] var toolongFields = []
for (var i in COLS) { for (var i in COLS) {
...@@ -406,7 +423,7 @@ function validateAndMsg() { ...@@ -406,7 +423,7 @@ function validateAndMsg() {
} }
// test length -------------------- // test length --------------------
else if (actualValue != null) { else if (mandatory || (actualValue != null && actualValue != "")) {
if (isExactN) { if (isExactN) {
// should never happen => trigger error // should never happen => trigger error
...@@ -455,6 +472,9 @@ function validateAndMsg() { ...@@ -455,6 +472,9 @@ function validateAndMsg() {
// length is handled by each input's maxlength // length is handled by each input's maxlength
// re change the jobDate from ISO to YYYY/MM/DD
if (jobDate.value.length) jobDate.value = jobDate.value.slice(0,10).replace(/-/g,"/")
// display (TODO setTimeout and fade) // display (TODO setTimeout and fade)
mainMessage.innerHTML = errorMessage mainMessage.innerHTML = errorMessage
return false return false
...@@ -588,11 +608,11 @@ nameInputs.forEach ( function(nameInput) { ...@@ -588,11 +608,11 @@ nameInputs.forEach ( function(nameInput) {
if(/[A-Z]/.test(txt)) { if(/[A-Z]/.test(txt)) {
var capsArr = txt.match(/[A-Z]/g) var capsArr = txt.match(/[A-Z]/g)
for (var i in capsArr) { for (var i in capsArr) {
apparentInitials += capsArr[i] + '.' apparentInitials += capsArr[i]
} }
} }
else { else {
apparentInitials += txt.charAt(0) + '.' apparentInitials += txt.charAt(0)
} }
} }
}) ; }) ;
...@@ -633,22 +653,6 @@ var pass2 = document.getElementById('password2') ...@@ -633,22 +653,6 @@ var pass2 = document.getElementById('password2')
var passMsg = document.getElementById('password_message') var passMsg = document.getElementById('password_message')
var passwords = [pass1, pass2] var passwords = [pass1, pass2]
// £DEBUG autofill ----------->8------
// fName.value = "Jean"
// lName.value = "Tartampion"
// initialsInput.value="JPP"
// document.getElementById('country').value = "France"
// email.value= makeRandomString(7)+"@om.fr"
// pass1.value="123456+789"
// pass2.value="123456+789"
// document.getElementById('jobtitle').value = "atitle"
// document.getElementById('keywords').value = "Blabla"
// document.getElementById('institution').value = "CNRS"
// basicEmailValidate(email.value)
// --------------------------->8------
passwords.forEach ( function(pass) { passwords.forEach ( function(pass) {
// could also be attached to form onchange but then called often for nothing // could also be attached to form onchange but then called often for nothing
pass.onkeyup = checkPassStatus pass.onkeyup = checkPassStatus
...@@ -686,7 +690,29 @@ function checkPassStatus() { ...@@ -686,7 +690,29 @@ function checkPassStatus() {
} }
if (!passStatus) passMsg.style.color = colorRed if (!passStatus) passMsg.style.color = colorRed
else passMsg.style.color = colorGreen else passMsg.style.color = colorGreen
}
// jobLookingDateStatus ~~~> is job date a valid date?
var jobBool = document.getElementById('job_bool')
var jobDate = document.getElementById('job_looking_date')
var jobDateMsg = document.getElementById('job_date_message')
jobDate.onkeyup = checkJobDateStatus
jobDate.onchange = checkJobDateStatus
function checkJobDateStatus() {
jobLookingDateStatus = (jobBool.value == "No" || validDate.test(jobDate.value))
if (!jobLookingDateStatus) {
jobDateMsg.style.color = colorRed
jobDateMsg.innerHTML = 'Date is not yet in the valid format YYYY/MM/DD'
} }
else {
jobDateMsg.style.color = colorGreen
jobDateMsg.innerHTML = 'Ok valid date!'
}
}
...@@ -725,13 +751,27 @@ $(function() { ...@@ -725,13 +751,27 @@ $(function() {
}); });
// autocomplete hon_title
$(function() {
var $hontitlesInput = $('#hon_title')
var hontitlesList = ["Student", "PhD Student", "Doctor"]
$hontitlesInput.autocomplete({
source: hontitlesList,
autoFocus: true,
select: function( event, ui ) {
$hontitlesInput[0].style.fontWeight = "bold"
}
});
});
// autocomplete position // autocomplete position
$(function() { $(function() {
var $jobtitlesInput = $('#jobtitle') var $jobtitlesInput = $('#position')
var jobtitlesList = ["Student", "Engineer", "capetown", var jobtitlesList = ["Engineer", "Lecturer", "Associate Professor", "Professor",
"PhD Student", "Dr", "Post-Doc",
"Lecturer", "Associate Professor", "Professor",
"Research Fellow", "Research Director", "Chairman"] "Research Fellow", "Research Director", "Chairman"]
$jobtitlesInput.autocomplete({ $jobtitlesInput.autocomplete({
...@@ -744,9 +784,10 @@ $(function() { ...@@ -744,9 +784,10 @@ $(function() {
}); });
}); });
// autocomplete institution // autocomplete institution
$(function() { $(function() {
var $institutionInput = $('#institution') var $orgInput = $('#org')
var orgList = [ var orgList = [
"Centre National de la Recherche Scientifique (CNRS)", "Centre National de la Recherche Scientifique (CNRS)",
...@@ -897,7 +938,7 @@ $(function() { ...@@ -897,7 +938,7 @@ $(function() {
"Institute of Computer Science of Czech Republic (AV ČR)", "Institute of Computer Science of Czech Republic (AV ČR)",
"Institute for Condensed Matter Physics of the National Academy of Sciences of Ukraine (ICMP)",] "Institute for Condensed Matter Physics of the National Academy of Sciences of Ukraine (ICMP)",]
$institutionInput.autocomplete({ $orgInput.autocomplete({
source: orgList, source: orgList,
autoFocus: true, autoFocus: true,
select: function( event, ui ) { select: function( event, ui ) {
...@@ -906,7 +947,7 @@ $(function() { ...@@ -906,7 +947,7 @@ $(function() {
// not tab because used to move on to next field // not tab because used to move on to next field
if(event.keyCode == 9) return false; if(event.keyCode == 9) return false;
$institutionInput[0].style.fontWeight = "bold" $orgInput[0].style.fontWeight = "bold"
} }
}); });
}); });
...@@ -1283,3 +1324,17 @@ $(function() { ...@@ -1283,3 +1324,17 @@ $(function() {
}); });
console.log("load OK") console.log("load OK")
// £DEBUG autofill ----------->8------
fName.value = "Jean"
lName.value = "Tartampion"
initialsInput.value="JPP"
document.getElementById('country').value = "France"
email.value= makeRandomString(7)+"@om.fr"
pass1.value="123456+789"
pass2.value="123456+789"
document.getElementById('position').value = "atitle"
document.getElementById('keywords').value = "Blabla"
document.getElementById('org').value = "CNRS"
basicEmailValidate(email.value)
// --------------------------->8------
...@@ -190,8 +190,15 @@ ...@@ -190,8 +190,15 @@
<h3 class="formcat"> About your job and research </h3> <h3 class="formcat"> About your job and research </h3>
<div class="question input-group"> <div class="question input-group">
<label for="jobtitle" class="smlabel input-group-addon">* Job Title</label> <label for="hon_title" class="smlabel input-group-addon"> Title </label>
<input id="jobtitle" name="jobtitle" maxlength="30" <input id="hon_title" name="hon_title" maxlength="30"
type="text" class="form-control autocomp" placeholder="titre"
onblur="makeBold(this)" onfocus="makeNormal(this)">
</div>
<div class="question input-group">
<label for="position" class="smlabel input-group-addon">* Job Title</label>
<input id="position" name="position" maxlength="30"
type="text" class="form-control autocomp" placeholder="titre" type="text" class="form-control autocomp" placeholder="titre"
onblur="makeBold(this)" onfocus="makeNormal(this)"> onblur="makeBold(this)" onfocus="makeNormal(this)">
</div> </div>
...@@ -210,16 +217,16 @@ ...@@ -210,16 +217,16 @@
<div class="question"> <div class="question">
<div class="input-group"> <div class="input-group">
<label for="institution" class="smlabel input-group-addon">* Parent Institution</label> <label for="org" class="smlabel input-group-addon">* Parent Institution</label>
<input id="institution" name="institution" maxlength="120" <input id="org" name="org" maxlength="120"
type="text" class="form-control autocomp" placeholder='eg "CNRS" or "University of Oxford"'> type="text" class="form-control autocomp" placeholder='eg "CNRS" or "University of Oxford"'>
</div> </div>
</div> </div>
<div class="question"> <div class="question">
<div class="input-group"> <div class="input-group">
<label for="institution_type" class="smlabel input-group-addon">* Institution Type</label> <label for="org_type" class="smlabel input-group-addon">* Institution Type</label>
<select id="institution_type" name="institution_type" <select id="org_type" name="org_type"
class="custom-select form-control" class="custom-select form-control"
onchange="if(this.value=='other'){otherInstDivStyle.display = 'block'} else {otherInstDivStyle.display='none'}"> onchange="if(this.value=='other'){otherInstDivStyle.display = 'block'} else {otherInstDivStyle.display='none'}">
<option selected value="university">University</option> <option selected value="university">University</option>
...@@ -234,10 +241,10 @@ ...@@ -234,10 +241,10 @@
</select> </select>
</div> </div>
<!-- Other institution type <=> only if previous choice == 5 --> <!-- Other institution type <=> only if previous choice == 5 -->
<div class="question conditional-q" id="other_institution_div"> <div class="question conditional-q" id="other_org_div">
<div class="input-group"> <div class="input-group">
<label for="other_institution_type" class="smlabel input-group-addon">Other type</label> <label for="other_org_type" class="smlabel input-group-addon">Other type</label>
<input id="other_institution_type" name="other_institution_type" maxlength="120" <input id="other_org_type" name="other_org_type" maxlength="120"
type="text" class="form-control" placeholder="Clarify here the type of your parent institution"> type="text" class="form-control" placeholder="Clarify here the type of your parent institution">
</div> </div>
</div> </div>
...@@ -255,8 +262,8 @@ ...@@ -255,8 +262,8 @@
<!-- Lab city <=> only for France --> <!-- Lab city <=> only for France -->
<div class="question conditional-q" id="team_city_div"> <div class="question conditional-q" id="team_city_div">
<div class="input-group"> <div class="input-group">
<label for="institution_city" class="smlabel input-group-addon">Lab city</label> <label for="org_city" class="smlabel input-group-addon">Lab city</label>
<input id="institution_city" name="institution_city" maxlength="50" <input id="org_city" name="org_city" maxlength="50"
type="text" class="form-control" placeholder="Ville de votre institution"> type="text" class="form-control" placeholder="Ville de votre institution">
</div> </div>
</div> </div>
...@@ -280,8 +287,25 @@ ...@@ -280,8 +287,25 @@
</div> </div>
</div> </div>
<div class="question">
<div class="input-group">
<label for="home_url" class="smlabel input-group-addon">Homepage</label>
<input id="home_url" name="home_url" maxlength="120"
type="text" class="form-control autocomp" placeholder='eg "http://www.wittyexample.org/~me"'>
</div>
</div>
<div class="question">
<p class="legend">Link to a picture of yours...</p>
<div class="input-group">
<label for="pic_url" class="smlabel input-group-addon">Picture link</label>
<input id="pic_url" name="pic_url" maxlength="120"
type="text" class="form-control autocomp" placeholder='eg "http://www.wittyexample.org/~me/my_great_pic.png"'>
</div>
</div>
<div class="question" style="margin-bottom:.5em" > <div class="question" style="margin-bottom:.5em" >
<p class="legend">Upload a picture of yours (max source size: 200kB)</p> <p class="legend">... or upload a picture (png, jpg or gif, max source size: 200kB)</p>
<!-- <p class="legend">Upload a picture of yours (max source size: 4MB, then the image will be reduced to 200kB)</p> --> <!-- <p class="legend">Upload a picture of yours (max source size: 4MB, then the image will be reduced to 200kB)</p> -->
<div class="input-group"> <div class="input-group">
<label for="pic_file" class="smlabel input-group-addon">Picture</label> <label for="pic_file" class="smlabel input-group-addon">Picture</label>
...@@ -320,6 +344,28 @@ ...@@ -320,6 +344,28 @@
<p class="legend">Optional, ~15 lines max (1200 chars)</p> <p class="legend">Optional, ~15 lines max (1200 chars)</p>
</div> </div>
<div class="question">
<div class="input-group">
<label for="job_bool" class="smlabel input-group-addon">Looking for a job?</label>
<select id="job_bool" name="job_bool"
class="custom-select form-control"
onchange="if(this.value=='Yes'){jobLookingDivStyle.display = 'block'} else {jobLookingDivStyle.display='none';jobDate.value=''}">
<option selected value="No" onclick="jobDate.value=''">No</option>
<option value="Yes" onclick="jobLookingDivStyle.display = 'block'">Yes</option>
</select>
</div>
<!-- job_looking_date_div <=> only if previous choice == Yes -->
<div class="question conditional-q" id="job_looking_div">
<p class="legend">Until when are you interested in job offers like post-doc positions, etc. ? (YYYY/MM/DD)</p>
<div class="input-group">
<label for="job_looking_date" class="smlabel input-group-addon">Job search limit</label>
<input id="job_looking_date" name="job_looking_date" maxlength="10"
type="text" class="form-control" placeholder="ex: 2019/09/30">
</div>
<p id="job_date_message" class="legend red" style="font-weight:bold"></p>
</div>
</div>
<!-- CNIL WARNING --> <!-- CNIL WARNING -->
<h3 class="formcat"> About your data </h3> <h3 class="formcat"> About your data </h3>
......
...@@ -82,8 +82,8 @@ ...@@ -82,8 +82,8 @@
<div class="spacer col-sm-1 col-md-1">&nbsp;</div> <div class="spacer col-sm-1 col-md-1">&nbsp;</div>
<div class="raw-responses col-sm-8 col-md-8" style="font-family:Calibri, sans-serif"> <div class="raw-responses col-sm-8 col-md-8" style="font-family:Calibri, sans-serif">
<h3>debug</h3> <h3>debug</h3>
{% for key in records %} {% for key in debug_records %}
<p> {{key}} {{records[key]}} </p> <p> {{key}} {{debug_records[key]}} </p>
{% endfor %} {% endfor %}
<p style="font-family: monospace; font-weight: bold"> <p style="font-family: monospace; font-weight: bold">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment