diff --git a/deploy.json b/deploy.json index 288fbd3..a730619 100644 --- a/deploy.json +++ b/deploy.json @@ -10,13 +10,6 @@ "dst": "/var/www/html/cc-test/.htaccess", "add-header-comment": false }, - "// service worker", - { - "type": "move", - "src": "site/sw.js", - "dst": "/var/www/html/cc-test/sw.js", - "add-header-comment": true - }, "// web sources", { "type": "move", @@ -36,7 +29,7 @@ "type": "move", "src": "site/css/", "dst": "/var/www/html/cc-test/css/", - "match": "^.*\\.(css|php)$", + "match": "^.*\\.(php)$", "add-header-comment": true }, { diff --git a/dev/docker/database/epicast/Dockerfile b/dev/docker/database/epicast/Dockerfile new file mode 100644 index 0000000..ca49dde --- /dev/null +++ b/dev/docker/database/epicast/Dockerfile @@ -0,0 +1,16 @@ +# start with the `delphi_database` image +FROM delphi_database + +# create the `epicast2` database +ENV MYSQL_DATABASE epicast2 + +# create the `epi` user account with a development-only password +ENV MYSQL_USER user +ENV MYSQL_PASSWORD pass + +# provide DDL which will create empty tables at container startup +# note that files are executed in order of filename alphabetically, so here +# destination files are named such that table definitions are executed prior to +# data insertions +COPY repos/delphi/www-epicast/src/ddl/epicast2.sql /docker-entrypoint-initdb.d/0_epicast2.sql +COPY repos/delphi/www-epicast/src/ddl/development_data.sql /docker-entrypoint-initdb.d/1_development_data.sql diff --git a/dev/docker/database/epicast/README.md b/dev/docker/database/epicast/README.md new file mode 100644 index 0000000..6b767cd --- /dev/null +++ b/dev/docker/database/epicast/README.md @@ -0,0 +1,27 @@ +# `delphi_database_epicast` + +This image extends Delphi's database by: + +- adding the `epi` user account +- adding the `epicast2` database +- creating and minimally populating tables in `epicast2` + +To start a container from this image, run: + +```bash +docker run --rm -p 13306:3306 \ + --network delphi-net --name delphi_database_epicast \ + delphi_database_epicast +``` + +For debugging purposes, you can interactively connect to the database inside +the container using a `mysql` client (either installed locally or supplied via +a docker image) like this: + +```bash +mysql --user=user --password=pass --port 13306 --host 127.0.0.1 epicast2 +``` + +Note that using host `localhost` may fail on some platforms as mysql will +attempt, and fail, to use a Unix socket. Using `127.0.0.1`, which implies +TCP/IP, works instead. diff --git a/dev/docker/web/epicast/Dockerfile b/dev/docker/web/epicast/Dockerfile new file mode 100644 index 0000000..2d60ddc --- /dev/null +++ b/dev/docker/web/epicast/Dockerfile @@ -0,0 +1,27 @@ +# start with the `delphi_web` image +FROM delphi_web + +# enable mod_rewrite, aka RewriteEngine +RUN a2enmod rewrite + +# enable mod_headers +RUN a2enmod headers + +# create an empty htpasswd file +RUN touch /var/www/passwords + +# deploy the Epicast website (see `www-epicast/deploy.json`) +# deploy files in an order that tries to take advantage of build caching + +COPY repos/delphi/www-epicast/site/images/* /var/www/html/images/ +COPY repos/delphi/www-epicast/site/images/flags/* /var/www/html/images/flags/ +COPY repos/delphi/www-epicast/site/data/* /var/www/html/data/ + +COPY repos/delphi/www-epicast/site/css/* /var/www/html/css/ +COPY repos/delphi/www-epicast/site/js/*.js /var/www/html/js/ +COPY repos/delphi/www-epicast/site/js/us-map /var/www/html/js/us-map +COPY repos/delphi/www-epicast/site/common/* /var/www/html/common/ +COPY repos/delphi/www-epicast/site/*.php /var/www/html/ + +# point to the local development database (overwrites `common/settings.php`) +COPY repos/delphi/www-epicast/dev/docker/web/epicast/assets/settings.php /var/www/html/common/ diff --git a/dev/docker/web/epicast/README.md b/dev/docker/web/epicast/README.md new file mode 100644 index 0000000..b7818ba --- /dev/null +++ b/dev/docker/web/epicast/README.md @@ -0,0 +1,24 @@ +# `delphi_web_epicast` + +This image starts with Delphi's web server and adds the sources necessary for +hosting the Epicast website. It further extends Delphi's web server by: + +- enabling the `mod_rewrite` extension +- enabling the `mod_headers` extension +- creating an empty `htpasswd` file + +This image includes the file +[`settings.php`](assets/settings.php), which points to a local +container running the +[`delphi_database_epicast` image](../../database/epicast/README.md). + +To start a container from this image, run: + +```bash +docker run --rm -p 10080:80 \ + --network delphi-net --name delphi_web_epicast \ + delphi_web_epicast +``` + +You should be able to login and interact with the website (e.g. submitting +predictions) by visiting `http://localhost:10080/` in a web browser. diff --git a/dev/docker/web/epicast/assets/settings.php b/dev/docker/web/epicast/assets/settings.php new file mode 100644 index 0000000..7307cf8 --- /dev/null +++ b/dev/docker/web/epicast/assets/settings.php @@ -0,0 +1,15 @@ + 'Delphi Developer', + 'email' => 'fake_email_address', +); +?> diff --git a/site/.htaccess b/site/.htaccess index 4c2fc5b..7d6fdd3 100644 --- a/site/.htaccess +++ b/site/.htaccess @@ -1,12 +1,13 @@ RewriteEngine On -## Resrtict access to known users ## +## Restrict access to known users or docker development ## AuthType Basic AuthName "Restricted Site: Crowdcast" AuthBasicProvider file AuthUserFile /var/www/passwords Require user cctest -## Resrtict access to known users ## +Require ip 172.16.0.0/12 +## Restrict access to known users or docker development ## ## Compress Files ## diff --git a/site/admin.php b/site/admin.php index 9bfd797..88c4d26 100644 --- a/site/admin.php +++ b/site/admin.php @@ -1,277 +1,279 @@ -', $field); - printf(' ', $field); -} -function sortBool(&$a, &$b) { - global $sortDir, $sortKey; - $value = 0; - if($a[$sortKey] && !$b[$sortKey]) { - $value = 1; - } - if(!$a[$sortKey] && $b[$sortKey]) { - $value = -1; - } - return $value * ($sortDir === 'a' ? 1 : -1); -} -function sortInt(&$a, &$b) { - global $sortDir, $sortKey; - $value = $a[$sortKey] - $b[$sortKey]; - return $value * ($sortDir === 'a' ? 1 : -1); -} -if(isAdmin($output)) { -?> -
- 0) { - $numForecastusers++; - } - } - } - foreach($u['submissions_hosp'] as &$s) { - if($s[0] === $output['epiweek']['round_epiweek']) { - $numForecasts_hosp += $s[1]; - if($s[1] > 0) { - $numForecastusers_hosp++; - } - } - } - foreach($emailTypes as &$type) { - if(getPreference($u, 'email_' . $type, 'int') === 1) { - $u['emails']++; - } - } - foreach($u['submissions'] as &$s) { - $u['total_submissions'] += $s[1]; - } - foreach($u['submissions_hosp'] as &$s) { - $u['total_submissions_hosp'] += $s[1]; - } - } - //PHP sort - if($sortFunc !== null) { - usort($output['userbase'], $sortFunc); - } - ?> -
-
- User Stats -
-
-
-
-
Total Registered Users
-
-
-
-
New Users Last 7 Days
-
-
-
-
Users Active Last 7 Days
-
-
-
-
Users Online Now
-
-
-
- -
-
- Regions and States -
-
-
-
-
Most Recent Report
-
-
-
-
Forecasts Received This Round
-
-
-
-
Users Participated This Round
-
-
-
- - -
-
- Userbase -
- Hopefully some day this will be too long to display on a single page. -
-
-
- - - - - - - - - - - - - - - -
Name EmailFirst Seen Last Seen PreferencesPresence Communication Submissions
', explode(' ', $u['first_seen'])) ?>', explode(' ', $u['last_seen'])) ?>', $k, $u['user_preferences'][$k]); - } else { - printf('%s: %s
', $k, $output['default_preferences'][$k]); - } - } - foreach(array_keys($u['user_preferences']) as $k) { - if(!isset($output['default_preferences'][$k])) { - printf('%s: %s
', $k, $u['user_preferences'][$k]); - } - } - ?>
- Online Now - - Offline - - Missing - - All Emails - 0) { ?> - Some Emails - - Do Not Contact - '); - foreach($u['submissions'] as &$s) { - printf('%s: %d
', formatEpiweek($s[0]), $s[1]); - } - printf('Total: %d
', $u['total_submissions']); - ?>
-
-
-
-
- Control Interface -
- Update user and system settings. -
-
-

The control interface is here.

-
-
- +', $field); + printf(' ', $field); +} +function sortBool(&$a, &$b) { + global $sortDir, $sortKey; + $value = 0; + if($a[$sortKey] && !$b[$sortKey]) { + $value = 1; + } + if(!$a[$sortKey] && $b[$sortKey]) { + $value = -1; + } + return $value * ($sortDir === 'a' ? 1 : -1); +} +function sortInt(&$a, &$b) { + global $sortDir, $sortKey; + $value = $a[$sortKey] - $b[$sortKey]; + return $value * ($sortDir === 'a' ? 1 : -1); +} +if(isAdmin($output)) { +?> +
+ 0) { + $numForecastusers++; + } + } + } + foreach($u['submissions_hosp'] as &$s) { + if($s[0] === $output['epiweek']['round_epiweek']) { + $numForecasts_hosp += $s[1]; + if($s[1] > 0) { + $numForecastusers_hosp++; + } + } + } + foreach($emailTypes as &$type) { + if(getPreference($u, 'email_' . $type, 'int') === 1) { + $u['emails']++; + } + } + foreach($u['submissions'] as &$s) { + $u['total_submissions'] += $s[1]; + } + foreach($u['submissions_hosp'] as &$s) { + $u['total_submissions_hosp'] += $s[1]; + } + } + //PHP sort + if($sortFunc !== null) { + usort($output['userbase'], $sortFunc); + } + ?> +
+
+ User Stats +
+
+
+
+
Total Registered Users
+
+
+
+
New Users Last 7 Days
+
+
+
+
Users Active Last 7 Days
+
+
+
+
Users Online Now
+
+
+
+ +
+
+ Regions and States +
+
+
+
+ +
Most Recent Report
+
+
+
+
Forecasts Received This Round
+
+
+
+
Users Participated This Round
+
+
+
+ + +
+
+ Userbase +
+ Hopefully some day this will be too long to display on a single page. +
+
+
+ + + + + + + + + + + + + + + +
Name EmailFirst Seen Last Seen PreferencesPresence Communication Submissions
', explode(' ', $u['first_seen'])) ?>', explode(' ', $u['last_seen'])) ?>', $k, $u['user_preferences'][$k]); + } else { + printf('%s: %s
', $k, $output['default_preferences'][$k]); + } + } + foreach(array_keys($u['user_preferences']) as $k) { + if(!isset($output['default_preferences'][$k])) { + printf('%s: %s
', $k, $u['user_preferences'][$k]); + } + } + ?>
+ Online Now + + Offline + + Missing + + All Emails + 0) { ?> + Some Emails + + Do Not Contact + '); + foreach($u['submissions'] as &$s) { + printf('%s: %d
', formatEpiweek($s[0]), $s[1]); + } + printf('Total: %d
', $u['total_submissions']); + ?>
+
+
+
+
+ Control Interface +
+ Update user and system settings. +
+
+

The control interface is here.

+
+
+ diff --git a/site/common/database.php b/site/common/database.php index fa054a0..95b88df 100644 --- a/site/common/database.php +++ b/site/common/database.php @@ -191,9 +191,7 @@ function getUserStats_hosp($dbh, &$output, $userID, $epiweek) { $output['result'] will contain the following values: 1 - Success 2 - Failure (table ec_fluv_round) - 3 - Failure (table epidata.fluview) - $output['epiweek']['round_epiweek'] - This round's identifier (can be ahead of data_epiweek) - $output['epiweek']['data_epiweek'] - The most recently published issue (from epidata.fluview) + $output['epiweek']['round_epiweek'] - This round's identifier (can be ahead of latest issue) $output['epiweek']['deadline'] - Deadline timestamp as a string (YYYY-MM-DD HH:MM:SS) $output['epiweek']['deadline_timestamp'] - Unix timestamp of deadline $output['epiweek']['remaining'] - An array containing days/hours/minutes/seconds remaining @@ -246,14 +244,6 @@ function getEpiweekInfo($dbh, &$output) { setResult($output, 2); return getResult($output); } - $result = $dbh->query('SELECT max(`issue`) AS `data_epiweek` FROM epidata.`fluview`'); - if($row = $result->fetch_assoc()) { - $output['epiweek']['data_epiweek'] = intval($row['data_epiweek']); - setResult($output, 1); - } else { - setResult($output, 3); - return getResult($output); - } return getResult($output); } @@ -357,7 +347,7 @@ function getRegions($dbh, &$output, $userID) { $regions[$region['id']] = $region; } $output['regions'] = &$regions; - + setResult($output, count($regions) == NUM_REGIONS ? 1 : 2); return getResult($output); } @@ -443,7 +433,7 @@ function getRegionsExtended($dbh, &$output, $userID) { if(getRegions($dbh, $output, $userID) !== 1) { return getResult($output); } - + //History and forecast for every region foreach($output['regions'] as &$r) { if(getPreference($output, 'advanced_prior', 'int') === 1) { @@ -451,16 +441,16 @@ function getRegionsExtended($dbh, &$output, $userID) { } else { $firstWeek = 200430; } - + if(getHistory($dbh, $output, $r['id'], $firstWeek) !== 1) { return getResult($output); } - + $r['history'] = $output['history']; if(loadForecast($dbh, $output, $userID, $r['id']) !== 1) { return getResult($output); } - + $r['forecast'] = $output['forecast']; } setResult($output, 1); @@ -596,7 +586,7 @@ function getAgeGroupsExtended($dbh, &$output, $userID) { 2 - Failure $output['history'] - Arrays of epiweeks and historical incidence (wILI) for the region */ -function getHistory($dbh, &$output, $regionID, $firstWeek) { +function getHistory($dbh, &$output, $regionID, $firstWeek) { $result = $dbh->query("SELECT fv.`epiweek`, fv.`wili` FROM epidata.`fluview` AS fv JOIN ( SELECT `epiweek`, max(`issue`) AS `latest` FROM epidata.`fluview` AS fv JOIN ec_fluv_regions AS reg ON reg.`fluview_name` = fv.`region` WHERE reg.`id` = {$regionID} AND fv.`epiweek` >= {$firstWeek} GROUP BY fv.`epiweek` ) AS issues ON fv.`epiweek` = issues.`epiweek` AND fv.`issue` = issues.`latest` JOIN ec_fluv_regions AS reg ON reg.`fluview_name` = fv.`region` WHERE reg.`id` = {$regionID} AND fv.`epiweek` >= {$firstWeek} ORDER BY fv.`epiweek` ASC"); $date = array(); $wili = array(); @@ -739,7 +729,7 @@ function listAgeGroups($dbh) { 2 - Failure */ function saveForecast($dbh, &$output, $userID, $regionID, $forecast, $commit) { - + $temp = array(); if(getEpiweekInfo($dbh, $temp) !== 1) { return getResult($temp); @@ -752,18 +742,18 @@ function saveForecast($dbh, &$output, $userID, $regionID, $forecast, $commit) { if($commit) { $dbh->query("INSERT INTO ec_fluv_submissions (`user_id`, `region_id`, `epiweek_now`, `date`) VALUES ({$userID}, {$regionID}, {$temp['epiweek']['round_epiweek']}, now())"); } - + $debug = false; if ($debug) { echo "-------saveForecast----\n"; echo $commit; } - - + + setResult($output, 1); - + getRegions($dbh, $output, $userID); - + return getResult($output); } @@ -840,7 +830,7 @@ function loadForecast($dbh, &$output, $userID, $regionID, $submitted=false) { echo "-----inside loadForecast------\n"; echo "user ID, region ID, submitted: "; echo $userID; - echo $regionID; + echo $regionID; echo $submitted; } @@ -849,7 +839,7 @@ function loadForecast($dbh, &$output, $userID, $regionID, $submitted=false) { if(getEpiweekInfo($dbh, $temp) !== 1) { return getResult($temp); } - + $result = $dbh->query("SELECT coalesce(max(`epiweek_now`), 0) `epiweek` FROM ec_fluv_submissions WHERE `user_id` = {$userID} AND `region_id` = {$regionID} AND `epiweek_now` < {$temp['epiweek']['round_epiweek']}"); } else { $result = $dbh->query("SELECT coalesce(max(`epiweek_now`), 0) `epiweek` FROM ec_fluv_forecast WHERE `user_id` = {$userID} AND `region_id` = {$regionID}"); @@ -863,17 +853,17 @@ function loadForecast($dbh, &$output, $userID, $regionID, $submitted=false) { $date = array(); $wili = array(); $result = $dbh->query("SELECT `epiweek_now`, `epiweek`, `wili` FROM ec_fluv_forecast f WHERE `user_id` = {$userID} AND `region_id` = {$regionID} AND `epiweek_now` = {$epiweek} ORDER BY f.`epiweek` ASC"); - + if ($debug and ($regionID == 1 or $regionID == 8)) { echo "epiweek: "; echo $epiweek; echo "\n"; } - + while($row = $result->fetch_assoc()) { array_push($date, intval($row['epiweek'])); array_push($wili, floatval($row['wili'])); - + if ($debug and ($regionID == 1 or $regionID == 8)) { echo intval($row['epiweek']); echo ", "; @@ -1041,7 +1031,7 @@ function registerUser_mturk_2019($dbh, $mturkID, $taskID) { $query = "INSERT INTO ec_fluv_users_mturk_2019 (`hash`, `name`, `email`, `first_seen`, `last_seen`, `taskID`) VALUES ('{$hash}', '{$escapedInput}', '{$email}', now(), now(), {$taskID})"; $dbh->query($query); - + $temp = array(); if(getEpiweekInfo_mturk($temp) !== 1) { return getResult($temp); @@ -1577,7 +1567,7 @@ function getECDCILI($dbh, &$output, $regionID, $firstWeek) { WHERE ed.`region` = \"{$country}\" AND ed.`epiweek` >= {$firstWeek} ORDER BY ed.`epiweek` ASC"; $result = $dbh->query($query); - + $date = array(); $wili = array(); while($row=$result->fetch_assoc()) { diff --git a/site/common/footer.php b/site/common/footer.php index a34496d..e17224e 100644 --- a/site/common/footer.php +++ b/site/common/footer.php @@ -1,7 +1,35 @@ - - + - - - + + + + diff --git a/site/common/header.php b/site/common/header.php index 27d1d96..792cafe 100644 --- a/site/common/header.php +++ b/site/common/header.php @@ -20,6 +20,7 @@ +