Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ The docker configuration also supports running your own frontend and backend ser
2. Download source code: `git clone https://github.com/shift-org/shift-docs.git`
3. Start shift site: `cd shift-docs ; ./shift up`
a. If you are running windows, you may need to use a WSL extension in order to execute code in a unix (bash) terminal.
4. If you're standing up the site for the first time, add database tables with the setup script: `./shift mysql-pipe < services/db/seed/setup.sql`.
5. Visit `https://localhost:4443/` . If this leads to an SSL error in chrome, you may try flipping this flag: chrome://flags/#allow-insecure-localhost
4. Visit `https://localhost:4443/` . If this leads to an SSL error in chrome, you may try flipping this flag: chrome://flags/#allow-insecure-localhost

Note that no changes to the filesystems **inside** the container should ever be needed; they read from your **local** filesystem so updating the local FS will show up in the container (perhaps after a restart). Updating, changing branches, etc can be done with git commands **outside** of the container (`git checkout otherbranch` or `git pull`).

Expand Down
122 changes: 36 additions & 86 deletions app/app.js
Original file line number Diff line number Diff line change
@@ -1,90 +1,40 @@
const express = require('express');
const config = require("./config");
const errors = require("./util/errors");
const nunjucks = require("./nunjucks");
const { initMail } = require("./emailer");
const knex = require("./knex"); // initialize on startup
const app = express();

// shift.conf for nginx sets the x-forward header
// and this says express can use it
app.set('trust proxy', true);

// allows ex. res.render('crawl.html');
nunjucks.express(app);

// modify every request
app.use(function (req, res, next) {
// add these two error shortcuts:
res.textError = (msg) => errors.textError(res, msg);
res.fieldError = (fields, msg) => errors.fieldError(res, fields, msg);
// tbd: the php sets this for every end point.
// maybe unneeded with the trust_proxy call above?
res.set('Access-Control-Allow-Origin', "*");
next()
});

// for development, allow the backend to serve the frontend.
// you can use "hugo --watch" to rebuild changes on demand.
if (config.site.staticFiles) {
const { makeFacade } = require('./facade');
makeFacade(app, config);
}

// handle application/x-www-form-urlencoded and application/json posts
// ( multipart posts are handled by their individual endpoints )
app.use(express.urlencoded({extended:false}), express.json());

// each of these is a javascript file
// containing a get ( or post ) export.
const endpoints = [
"crawl",
"delete_event",
"events",
"ical",
"manage_event",
"retrieve_event",
"search",
"ride_count"
];

// host each of those endpoint files at a php-like url:
// note: require() is synchronous.
endpoints.forEach((ep) => {
const apipath = `/api/${ep}.php`;
const endpoint = require(`./endpoints/${ep}.js`);
if (endpoint.get) {
app.get(apipath, endpoint.get);
}
if (endpoint.post) {
if (Array.isArray(endpoint.post)) {
app.post(apipath, ...endpoint.post);
} else {
app.post(apipath, endpoint.post);
}
}
});

app.use(function(err, req, res, next) {
res.sendStatus(500);
console.error(err.stack);
});
/**
* The main entry point for the node container
*/
const config = require('./config');
const { initMail } = require( './emailer');
const app = require( './appEndpoints');
const db = require('./db'); // initialize on startup
const tables = require("./models/tables");

// connect to the db
db.initialize().then(async () => {
// create db tables
await tables.createTables();

// connect to the smtp server
await initMail().then(hostName => {
console.log("okay: verified email host", hostName);
}).catch(e => {
console.error("failed smtp verification because", e.toString());
}).finally(_ => {
console.log("and emails will log to", config.email.logfile || "console");
});

const verifyMail = initMail().then(hostName=> {
console.log("okay: verified email host", hostName);
}).catch(e=> {
console.error("failed smtp verification:", e.toString());
}).finally(_ => {
console.log("and emails will log to", config.email.logfile || "console");
});
Promise.all([knex.initialize(), verifyMail]).then(_ => {
// start a webserver to listen to all requests
const port = config.site.listen;
app.listen(port, _ => {
// NOTE: the ./shift script listens for this message!
console.log(`${config.site.name} listening at ${config.site.url()}`)
app.emit("ready"); // raise a signal for testing.
app.emit("ready"); // raise a signal for testing? todo: document what this does.
// use a timeout to appear after the vite message;
// reduces confusion about which port to browse to.
setTimeout(() => {
// NOTE: the ./shift script listens for this message!
console.log("\n=======================================");
console.group();
console.info(`${config.site.name} listening.`);
console.info(`Browse to \x1b[36m${config.site.url()}\x1b[0m to see the site.`)
console.groupEnd();
console.log("=======================================");
}, 1000);
});
});

// for testing
module.exports = app;
});
74 changes: 74 additions & 0 deletions app/appEndpoints.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
const express = require('express');
const config = require("./config");
const errors = require("./util/errors");
const nunjucks = require("./nunjucks");
const { initMail } = require("./emailer");
const knex = require("./db"); // initialize on startup
const app = express();

// shift.conf for nginx sets the x-forward header
// and this says express can use it
app.set('trust proxy', true);

// allows ex. res.render('crawl.html');
nunjucks.express(app);

// modify every request
app.use(function (req, res, next) {
// add these two error shortcuts:
res.textError = (msg) => errors.textError(res, msg);
res.fieldError = (fields, msg) => errors.fieldError(res, fields, msg);
// tbd: the php sets this for every end point.
// maybe unneeded with the trust_proxy call above?
res.set('Access-Control-Allow-Origin', "*");
next()
});

// for development, allow the backend to serve the frontend.
// you can use "hugo --watch" to rebuild changes on demand.
if (config.site.staticFiles) {
const { makeFacade } = require('./facade');
makeFacade(app, config);
}

// handle application/x-www-form-urlencoded and application/json posts
// ( multipart posts are handled by their individual endpoints )
app.use(express.urlencoded({extended:false}), express.json());

// each of these is a javascript file
// containing a get ( or post ) export.
const endpoints = [
"crawl",
"delete_event",
"events",
"ical",
"manage_event",
"retrieve_event",
"search",
"ride_count"
];

// host each of those endpoint files at a php-like url:
// note: require() is synchronous.
endpoints.forEach((ep) => {
const apipath = `/api/${ep}.php`;
const endpoint = require(`./endpoints/${ep}.js`);
if (endpoint.get) {
app.get(apipath, endpoint.get);
}
if (endpoint.post) {
if (Array.isArray(endpoint.post)) {
app.post(apipath, ...endpoint.post);
} else {
app.post(apipath, endpoint.post);
}
}
});

app.use(function(err, req, res, next) {
res.sendStatus(500);
console.error(err.stack);
});

// for testing
module.exports = app;
78 changes: 68 additions & 10 deletions app/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,28 @@ const listen = env_default('NODE_PORT', 3080);
// the user facing server.
const siteHost = siteUrl(listen);

// location of app.js ( same as config.cs )
const appPath = path.resolve(__dirname);
// location of app.js ( same as config.js )
const appPath = path.resolve(__dirname);

// for max file size
const bytesPerMeg = 1024*1024;

const staticFiles = env_default('SHIFT_STATIC_FILES');

const isTesting = !!process.env.npm_lifecycle_event.match(/test$/);
// read the command line parameter for db configuration
const dbType = env_default('npm_config_db');
const dbDebug = !!env_default('npm_config_db_debug');

const config = {
appPath,
api: {
header: 'Api-Version',
version: "3.59.3",
},
db: {
host: env_default('MYSQL_HOST', 'db'),
port: 3306, // standard mysql port.
user: env_default('MYSQL_USER', 'shift'),
pass: env_default('MYSQL_PASSWORD', 'ok124'),
name: env_default('MYSQL_DATABASE', 'shift'),
type: "mysql2", // name of driver, installed by npm
},
db: getDatabaseConfig(dbType, isTesting),
// maybe bad, but some code likes to know:
isTesting,
// a nodemailer friendly config, or false if smtp is not configured.
smtp: getSmtpSettings(),
site: {
Expand Down Expand Up @@ -194,3 +194,61 @@ function getSmtpSettings() {
};
}
}

// our semi-agnostic database configuration
function getDatabaseConfig(dbType, isTesting) {
// dbType comes from the command-line
// if nothing was specfied, use the MYSQL_DATABASE environment variable
const env = env_default('MYSQL_DATABASE')
if (!dbType && env) {
dbType = env.startsWith("sqlite") ? env : null;
}
if (!dbType) {
dbType = isTesting ? 'sqlite' : 'mysql'
}
const [name, parts] = dbType.split(':');
const config = {
mysql: !isTesting ? getMysqlDefault : getMysqlTesting,
sqlite: getSqliteConfig,
}
if (!name in config) {
throw new Error(`unknown database type '${dbType}'`)
}
return {
type: name,
connect: config[name](parts),
debug: dbDebug,
}
}

// the default for mysql when running dev or production
function getMysqlDefault() {
return {
host: env_default('MYSQL_HOST', 'db'),
port: env_default('MYSQL_PORT', 3306), // standard mysql port.
user: env_default('MYSQL_USER', 'shift'),
pass: env_default('MYSQL_PASSWORD', 'ok124'),
name: env_default('MYSQL_DATABASE', 'shift'),
}
}

// the default for mysql when running tests
function getMysqlTesting() {
return {
host: "localhost",
port: 3308, // custom EXTERNAL port to avoid conflicts
user: 'shift_test',
pass: 'shift_test',
name: 'shift_test',
}
}

// the default for sqlite
// if filename is null, it uses a memory database
// paths are relative to npm's starting path.
function getSqliteConfig(filename) {
const connection = !filename ? ":memory:" : path.resolve(appPath, filename);
return {
name: connection
};
}
Loading