diff --git a/.env.example b/.env.example
index e779af67..0782e9ba 100644
--- a/.env.example
+++ b/.env.example
@@ -1,20 +1,27 @@
# You will need a .env file to use with the Docker containers. This is set up for localhost use with the Docker Compose fullstack.
+# APP_HTTP_LISTEN_PORT is the TCP port used to access the Node application's HTTP interface (usually by a reverse proxy).
+APP_HTTP_HOST="nm3clol-express-app"
+# APP_HTTP_LISTEN_PORT is the TCP port used to access the Node application's HTTP interface (usually by a reverse proxy).
+APP_HTTP_PORT=3000
+# APP_URL is the URL used to access the Node application (usually by a reverse proxy).
+APP_HTTP_URL="http://${APP_HTTP_HOST}:${APP_HTTP_PORT}"
+
# SITE_NAME is used for page generation.
SITE_NAME="(dev) No Moss 3 Carbo Landfill Online Localhost"
# SITE_HOST is used for generating links for the search index. (If you leave this blank it should work using relative paths.)
-SITE_HOST="localhost"
+SITE_HOST="${APP_HTTP_HOST}"
# SITE_URL is used for generating links for the search index. (If you leave this blank it should work using relative paths.)
-SITE_URL="https://${SITE_HOST}"
-# WELCOME_MSG is used for the homepage instead of "Welcome to ${SITE_NAME}!"
-WELCOME_MSG="Devel' It Up, Developer!"
+SITE_URL="${APP_HTTP_URL}"
+# WELCOME_MESSAGE is used for the homepage instead of "Welcome to ${SITE_NAME}!"
+SITE_WELCOME_MESSAGE="Devel' It Up, Developer!"
-# APP_HTTP_LISTEN_PORT is the TCP port used to access the Node application's HTTP interface (usually by a reverse proxy).
-APP_HTTP_HOST="nm3clol"
-# APP_HTTP_LISTEN_PORT is the TCP port used to access the Node application's HTTP interface (usually by a reverse proxy).
-APP_HTTP_LISTEN_PORT=3000
-# APP_URL is the URL used to access the Node application (usually by a reverse proxy).
-APP_HTTP_URL="http://${APP_HTTP_HOST}:${APP_HTTP_LISTEN_PORT}"
+# PUBLIC_DIR is the relative path to the directory to this project root for the public files
+PUBLIC_DIR="../nm3clol-public"
+# PAGES_DIR is the relative path to the directory to this project root for the pages rather than public files
+PAGES_DIR="pages"
+# STATIC_DIR is the relative path to the directory to this project root for the static asset files
+STATIC_DIR="static"
# SOLR_DOCS_HOST is the host for Apache Solr's core for indexed documents.
SOLR_DOCS_HOST="solr"
@@ -40,6 +47,3 @@ TIKA_HOST="tika"
TIKA_PORT=9998
# TIKA_URL is the URL to access the host running Apache Tika.
TIKA_URL="http://${TIKA_HOST}:${TIKA_PORT}"
-
-# PUBLIC_DIR is the directory relative to this project root for the public files
-PUBLIC_DIR="../nm3clol-public"
diff --git a/app/config.js b/app/config.js
new file mode 100644
index 00000000..af794eae
--- /dev/null
+++ b/app/config.js
@@ -0,0 +1,51 @@
+console.log(`Configuring .env and expanding .env to include environment variable references.`);
+
+const path = require('path');
+const dotenv = require('dotenv');
+const dotenvExpand = require('dotenv-expand');
+
+let env = dotenv.config();
+dotenvExpand.expand(env);
+
+const getAppHttpHost = () => env.APP_HTTP_HOST||'nm3clol-express-app';
+const getAppHttpPort = () => parseInt(env.APP_HTTP_PORT||env.PORT||3000);
+const getAppHttpUrl = () => {
+ return env.APP_HTTP_URL || `http://${getAppHttpHost() + ((getAppHttpPort() === 80) ? '' : ':' + getAppHttpPort())}`
+};
+
+const getSiteName = () => env.SITE_NAME||"(dev) No Moss 3 Carbo Landfill Online Localhost";
+const getSiteWelcomeMessage = () => env.SITE_WELCOME_MESSAGE||"Devel' It Up, Developer!";
+const getSiteHost = () => env.SITE_HOST||"localhost";
+const getSiteUrl = () => env.SITE_URL||getAppHttpUrl();
+
+const getPublicPath = () => path.join(__dirname, '..', (env.PUBLIC_DIR||path.join('..', 'nm3clol-public')).replaceAll('\\', path.sep).replaceAll('/', path.sep));
+const getPagesPath = () => path.join(__dirname, '..', (env.PAGES_DIR||'pages').replaceAll('\\', path.sep).replaceAll('/', path.sep));
+const getStaticPath = () => path.join(__dirname, '..', (env.STATIC_DIR||'static').replaceAll('\\', path.sep).replaceAll('/', path.sep));
+
+const getSolrDocsHost = () => env.SOLR_DOCS_HOST||'solr';
+const getSolrDocsPort = () => parseInt(env.SOLR_DOCS_PORT||8983);
+const getSolrDocsCore = () => env.SOLR_DOCS_CORE||'nm3clol_core';
+const getSolrLawHost = () => env.SOLR_LAW_HOST||getSolrDocsHost();
+const getSolrLawPort = () => parseInt(env.SOLR_LAW_PORT||getSolrDocsPort());
+const getSolrLawCore = () => env.SOLR_LAW_CORE||'vacode_core';
+
+module.exports = {
+ config: {
+ publicPath: getPublicPath(),
+ pagesPath: getPagesPath(),
+ staticPath: getStaticPath(),
+ siteName: getSiteName(),
+ siteWelcomeMessage: getSiteWelcomeMessage(),
+ siteHost: getSiteHost(),
+ siteUrl: getSiteUrl(),
+ appHttpHost: getAppHttpHost(),
+ appHttpPort: getAppHttpPort(),
+ appHttpUrl: getAppHttpUrl(),
+ solrDocsHost: getSolrDocsHost(),
+ solrDocsPort: getSolrDocsPort(),
+ solrDocsCore: getSolrDocsCore(),
+ solrLawHost: getSolrLawHost(),
+ solrLawPort: getSolrLawPort(),
+ solrLawCore: getSolrLawCore(),
+ }
+};
diff --git a/routes/search.js b/app/routes/search.js
similarity index 95%
rename from routes/search.js
rename to app/routes/search.js
index e660167f..7b15aeb8 100644
--- a/routes/search.js
+++ b/app/routes/search.js
@@ -1,9 +1,11 @@
+console.log(`Loading nm3clol-express-app search router module...`);
+
const express = require('express');
const router = express.Router();
const { parse, toString } = require('lucene');
const { createClient, Query } = require('solr-client');
-const solrConfig = { host: process.env.SOLR_DOCS_HOST||'solr', port: process.env.SOLR_DOCS_PORT||8983, core: process.env.SOLR_DOCS_CORE_NAME||'nm3clol_core' };
-const helpers = require('../views/helpers/functions');
+const { config } = require('../config');
+const helpers = require('../../views/helpers/functions');
router.get('/', (req, res) => {
// Extract paging parameters from request query parameters
@@ -37,7 +39,7 @@ router.get('/', (req, res) => {
usePhraseHighlighter: true,
}});
// Create a Solr client
- const solrClient = createClient({ host: 'solr.services.cleveland.daball.me', port: 8983, core: 'my_core' });
+ const solrClient = createClient({ host: config.solrDocsHost, port: config.solrDocsPort, core: config.solrDocsCore });
solrClient.search(solrQuery)
.then(solrResponse => {
//console.log(require('util').inspect(solrResponse, { showHidden: true, depth: null, colors: true }));
diff --git a/app/server.js b/app/server.js
index 4a0d264d..d312343f 100644
--- a/app/server.js
+++ b/app/server.js
@@ -1,3 +1,5 @@
+console.log(`Starting up nm3clol-express-app...`);
+
const express = require('express');
const axios = require('axios');
const app = express();
@@ -6,18 +8,13 @@ const path = require('path');
const glob = require('glob');
const matter = require('gray-matter');
const ejs = require('ejs');
+const { config } = require('./config');
const helpers = require('../views/helpers/functions');
-const search = require('../routes/search');
+const search = require('./routes/search');
const fs = require('fs');
-const dotenv = require('dotenv');
-const dotenvExpand = require('dotenv-expand');
-dotenv.config();
-dotenvExpand.expand(process.env);
// const advancedSearch = require('../routes/advanced-search');
-const publicPath = path.join(__dirname, '..', process.env.PUBLIC_DIR.replaceAll('\\', path.sep).replaceAll('/', path.sep));
-// Port number for HTTP server
-const port = process.env.PORT||3000;
+console.log(`Running app configuration:`, config);
// Set EJS as the view engine
app.set('view engine', 'ejs');
@@ -41,14 +38,14 @@ app.use(express.json());
// res.send('Hello World!');
// })
-console.log("Setting route for /ads.txt");
-app.get('/ads.txt', (req, res) => {
- res.setHeader("Content-Type", "text/plain");
- res.setHeader("Cache-Control", "no-cache");
- res.send(`google.com, pub-8937572456576531, DIRECT, f08c47fec0942fa0`);
-});
+// console.log("Setting route for /ads.txt");
+// app.get('/ads.txt', (req, res) => {
+// res.setHeader("Content-Type", "text/plain");
+// res.setHeader("Cache-Control", "no-cache");
+// res.send(`google.com, pub-8937572456576531, DIRECT, f08c47fec0942fa0`);
+// });
-console.log("Setting route for /robots.txt");
+console.log(`Serving /robots.txt from memory.`);
app.get('/robots.txt', (req, res) => {
res.setHeader("Content-Type", "text/plain");
res.setHeader("Cache-Control", "no-cache");
@@ -63,25 +60,29 @@ Allow: /
});
// Search endpoints
-console.log("Setting routes for /search");
+console.log(`Serving /search using search router.`);
app.use('/search', search.router);
// app.use('/advanced-search', advancedSearch.router);
// Endpoints for all the site's pages.
-console.log("Scanning for pages to create routes");
-glob.globSync('pages/**/*.md', {
- cwd: path.join(__dirname, '..'),
+console.log(`Scanning for pages in ${config.pagesPath} to create routes.`);
+glob.globSync('**/*.md', {
+ cwd: config.pagesPath,
matchBase: true,
follow: true,
}).forEach((filePath) => {
const expressRoutePathFromFilePath = (filePath) => {
- return filePath.substring('pages'.length, filePath.length - path.extname(filePath).length).replaceAll(path.sep, path.posix.sep);
+ filePath = filePath.substring(0, filePath.length - path.extname(filePath).length).replaceAll(path.sep, path.posix.sep);
+ if (!filePath.startsWith('/') && filePath.length > 0) {
+ filePath = `/${filePath}`;
+ }
+ return filePath;
};
const route = expressRoutePathFromFilePath(filePath);
- const fullFilePath = path.join(__dirname, '..', filePath);
+ const fullFilePath = path.join(config.pagesPath, filePath);
let paths = route.split(path.posix.sep);
paths[0] = 'public';
- console.log(`Setting route for ${route}`);
+ console.log(`Serving ${route} route as a page at ${fullFilePath}.`);
app.get(route, async (req, res) => {
const fm = matter.read(fullFilePath);
const fmData = { fm: fm.data, excerpt: fm.excerpt };
@@ -91,7 +92,7 @@ glob.globSync('pages/**/*.md', {
});
});
-// console.log("Scanning for documents to create routes");
+// console.log("Scanning for documents to create routes.");
// glob.globSync('**/*{.pdf,.docx,.xlsx,.pptx,.doc,.xls,.ppt}', {
// cwd: path.join(__dirname, '..', 'public'),
// matchBase: true,
@@ -114,7 +115,8 @@ glob.globSync('pages/**/*.md', {
// });
// });
-console.log("Scanning for web archive HTML documents to create routes");
+//TODO: Rewrite this facility so that it utilizes Git index as a filesystem.
+console.log("Scanning for web archive HTML documents to create routes.");
glob.globSync('Web_Site_Archives/**/*{.htm,.html}', {
cwd: path.join(__dirname, '..', 'public'),
matchBase: true,
@@ -136,8 +138,8 @@ glob.globSync('Web_Site_Archives/**/*{.htm,.html}', {
});
-// Endpoints for all the site's YouTube videos.
-console.log("Scanning for archived videos to create routes");
+//TODO: Rewrite this facility so that it utilizes Git index as a filesystem.
+console.log("Scanning for archived videos to create routes.");
glob.globSync(['Russell_County/Board_of_Supervisors/YouTube_Archive/**/*.info.json', 'Virginia_Energy/YouTube_Archive/**/*.info.json', 'Virginia_Governor/**/*.info.json'], {
cwd: path.join(__dirname, '..', 'public'),
matchBase: true,
@@ -187,10 +189,10 @@ glob.globSync(['Russell_County/Board_of_Supervisors/YouTube_Archive/**/*.info.js
//app.get('/OCR-Encoded-PDFs/Russell-County-Web-Site_2024-02-13_19_50_Modified-With-OCR-Encoding**', rewriter.rewrite('/Web_Site_Archives/Russell_County_Web_Site-2024-02-13_19_50_Modified_With_OCR_Encoding/$1'));
-console.log(`Setting routes for /vendor/**/*`);;
+console.log(`Serving /vendor/**/* route for all files in ${path.join(config.staticPath, 'vendor')}`);;
app.get('/vendor/**/*', async (req, res) => {
await serve(req, res, {
- public: path.join(__dirname, '..', 'static'),
+ public: config.staticPath,
symlinks: true,
trailingSlash: true,
cleanUrls: false,
@@ -204,10 +206,10 @@ app.get('/vendor/**/*', async (req, res) => {
});
});
-console.log(`Setting routes for /css/*.css`);;
+console.log(`Serving /css/*.css route for all files in ${path.join(config.staticPath, 'css')}`);;
app.get('/css/*.css', async (req, res) => {
await serve(req, res, {
- public: path.join(__dirname, '..', 'static'),
+ public: config.staticPath,
symlinks: true,
trailingSlash: true,
cleanUrls: false,
@@ -221,10 +223,10 @@ app.get('/css/*.css', async (req, res) => {
});
});
-console.log(`Setting routes for /svg/*.svg`);;
+console.log(`Serving /svg/*.svg route for all files in ${path.join(config.staticPath, 'svg')}`);;
app.get('/svg/*.svg', async (req, res) => {
await serve(req, res, {
- public: path.join(__dirname, '..', 'static'),
+ public: config.staticPath,
symlinks: true,
trailingSlash: true,
cleanUrls: false,
@@ -238,10 +240,11 @@ app.get('/svg/*.svg', async (req, res) => {
});
});
-console.log(`Setting route for *`);
+//TODO: Rewrite this facility so that it utilizes Git index as a filesystem.
+console.log(`Serving * default route for all files in ${config.publicPath}`);;
app.get('*', async (req, res) => {
await serve(req, res, {
- public: publicPath,
+ public: config.publicPath,
symlinks: true,
trailingSlash: true,
cleanUrls: false,
@@ -276,6 +279,11 @@ app.get('*', async (req, res) => {
});
// Start server
-app.listen(port, () => {
- console.log(`no-moss-3-carbo-landfill-library.online app listening on port ${port}`);
+app.listen(config.appHttpPort, () => {
+ console.log(`nm3clol-express-app HTTP server listening on port ${config.appHttpPort}.`)
+ console.log(`To access your app, you can use the localhost URL, http://localhost:${config.appHttpPort}.`);
+ console.log(`To access your app, you can use the 127.0.0.1 host, http://127.0.0.1:${config.appHttpPort}.`);
+ console.log(`To access your app, you can use the ::1 host, http://[::1]:${config.appHttpPort}.`);
+ console.log(`To access your app, you might can use the app host name, ${config.appHttpUrl}.`);
+ console.log(`This app is configured to use the web site URL, ${config.siteUrl}.`);
});
diff --git a/views/directory.ejs b/views/directory.ejs
index 680a9bc0..4040eb7a 100644
--- a/views/directory.ejs
+++ b/views/directory.ejs
@@ -18,9 +18,9 @@
<% if (h.shouldShowDirectorySeparator({index})) { %>
›
<% } %>
- <% if (h.shouldShowWelcomeBanner({paths})) { %>
+ <% if (h.shouldShowSiteWelcomeMessage({paths})) { %>
- <%= h.getWelcomeBanner() %>
+ <%= h.getSiteWelcomeMessage() %>
<% } else if (h.shouldOmitLinkOnLastBreadcrumb({paths, index})) { %>
<%= h.trimSlashes({path: value.name}).replaceAll('_', ' ') %>
<% } else { %>
diff --git a/views/helpers/functions.js b/views/helpers/functions.js
index 1fcba0d8..753e0d43 100644
--- a/views/helpers/functions.js
+++ b/views/helpers/functions.js
@@ -1,7 +1,7 @@
const path = require('path');
const glob = require('glob');
const fs = require('fs');
-const process = require('process');
+const config = require('../../app/config');
const markdownit = require('markdown-it');
var markdownItAttrs = require('markdown-it-attrs');
const md = markdownit({
@@ -18,9 +18,7 @@ const md = markdownit({
);
const moment = require('moment-timezone').tz.setDefault("UTC");
-const getSiteName = () => {
- return process.env.SITE_NAME || '(dev) No Moss 3 Carbo Landfill Online Localhost';
-}
+const getSiteName = config.getSiteName;
const trimSlashes = ({path}) => {
return path.replace(/^[\/\\]|[\/\\]$/g, '');
@@ -40,11 +38,9 @@ const getDirectoryTitle = ({directory}) => {
.join(' - ');
return (directory=="public") ? getSiteName() : `${title} - ${getSiteName()}`;
};
-const getWelcomeBanner = () => {
- return process.env.WELCOME_MSG || `Welcome to ${getSiteName()}!`;
-};
+const getSiteWelcomeMessage = config.getSiteWelcomeMessage;
const shouldShowDirectorySeparator = ({index}) => (index > 0);
-const shouldShowWelcomeBanner = ({paths}) => (paths.length == 1);
+const shouldShowSiteWelcomeMessage = ({paths}) => (paths.length == 1);
const shouldOmitLinkOnLastBreadcrumb = ({paths, index}) => (index == paths.length-1);
const resolveReadmeFile = ({directory}) => {
@@ -88,7 +84,7 @@ const renderArchive = (html, paths) => {
// Header and Footer content
const headHeaderContent = ``;
const headFooterContent = `
-
+