Skip to content
Open
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
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,19 @@ 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`).

So - now you can hopefully access the site. But a real end-toend test of yoursetup, would be creating an event:
Now, hopefully access the site. To test your setup, create event:

1. visit https://localhost:4443/addevent/
2. fill out all required fields (ones marked with an asterisk), for a date a day or two in the future.
3. save the event (fix any validation errors around missing fields to ensure it saves)
4. In production, we send you an email with a link to confirm the ride listing; we also write a copy of that email to the file `services/node/shift-mail.log`. For local development, we don't actually send the email, so get the confirmation link from that mail log, visit it, and hit publish event
5. hopefully see your event on the https://localhost:4443/calendar page!
1. Visit https://localhost:4443/addevent/
2. Fill out all required fields (ones marked with an asterisk), for a date a day or two in the future.
3. Save the event (fix any validation errors around missing fields to ensure it saves)
4. In production, the backend would send you an email with a link to confirm the ride listing. During local development, we don't actually send the email. Instead, the confirmation link gets logged to Docker. To see those logs, run `./shift logs node`, and visit the link logged "publish" your ride. ( The link will look something like: `https://localhost:4443/addevent/edit-17-10b24786f5b14e4595cb8c988002a536` )
5. After publishing, if everything succeeds, you should be able to see your event on the https://localhost:4443/calendar page!

If you would like to create multiple test events, you can also run: `./shift compose makeFakeEvents`

## Important Project Files

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