Add everything up until the point of making it actually work :)

parent abb8142d
// ----------------------------------------------------------------------------
"use strict"
// core
const path = require('path')
// npm
const pg = require('pg')
const pgpatcher = require('pg-patcher')
const pgx = require('pg-x')
const log = require('logfmtr').default()
// local
const env = require('./env.js')
// ----------------------------------------------------------------------------
// setup
const databasePatchLevel = 3
// create a pool
const pool = new pg.Pool({
connectionString: env.databaseUrl,
idleTimeoutMillis: env.isProd ? 1000 : 60 * 1000,
})
function patch(callback) {
log.info({ level : databasePatchLevel }, 'patching-database')
const opts = {
dir: path.join(__dirname, '..', 'schema'),
logger: console.log.bind(console),
}
pgpatcher(pool, databasePatchLevel, opts, callback)
}
// ----------------------------------------------------------------------------
module.exports = {
// `pg.Pool`
pool,
// patch
patch,
}
// ----------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// core
const fs = require('fs')
const path = require('path')
// npm
const express = require('express')
const bodyParser = require('body-parser')
const compression = require('compression')
const favicon = require('serve-favicon')
const errorHandler = require('errorhandler')
const LogFmtr = require('logfmtr')
const yid = require('yid')
const zid = require('zid')
// local
const pkg = require('../package.json')
const env = require('./env.js')
const stats = require('./stats.js')
// --------------------------------------------------------------------------------------------------------------------
// setup
const log = LogFmtr.default()
// create the sitemap
const sitemap = [
`${env.baseUrl}/`,
]
const sitemapTxt = sitemap.join('\n') + '\n'
// --------------------------------------------------------------------------------------------------------------------
// app
const app = express()
app.set('view engine', 'pug')
app.enable('strict routing')
app.enable('case sensitive routing')
app.disable('x-powered-by')
if (env.isProd) {
app.enable('trust proxy')
}
app.locals.pkg = pkg
app.locals.env = env
// middleware
app.use(compression())
app.use(favicon(path.join(__dirname, '..', 'public', 'favicon.ico')))
app.use(express.static('public'))
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
// --------------------------------------------------------------------------------------------------------------------
// middleware
app.use((req, res, next) => {
// add a Request ID
req._rid = yid()
// create a RequestID and set it on the `req.log`
req.log = log.withFields({ rid: req._rid })
next()
})
app.use(LogFmtr.middleware)
app.use((req, res, next) => {
// From: http://blog.netdna.com/opensource/bootstrapcdn/accept-encoding-its-vary-important/
res.setHeader('Vary', 'Accept-Encoding')
next()
})
// --------------------------------------------------------------------------------------------------------------------
// routes
app.get(
'/',
(req, res) => {
stats.home.inc()
res.render('index', {
menu: 'home',
})
}
)
app.get(
'/stats',
(req, res, next) => {
let finished = false
let got = 0
let currentStats = {}
// get some bits
stats.pages.forEach((hitName) => {
stats[hitName].values((err, data) => {
if ( finished ) return
if (err) {
finished = true
return next(err)
}
got += 1
// save this hit
data.forEach((hit) => {
currentStats[hit.ts] = currentStats[hit.ts] || {}
currentStats[hit.ts][hitName] = hit.val
})
// if we've got all the results, render the page
if ( got === stats.pages.length ) {
finished = true
res.render('stats', { stats : currentStats, title : 'stats' })
}
})
})
}
)
app.get(
'/sitemap.txt',
(req, res) => {
res.setHeader('Content-Type', 'text/plain')
res.send(sitemapTxt)
}
)
app.use((err, req, res, next) => {
// connect.multipart() says the file is too big
if (err.status === 413 ) {
if ( req.xhr ) {
const data = {
ok : false,
msg : 'File is too big, should be less than 5MB.',
}
return res.json(data)
}
else {
res.setHeader('Content-Type', 'text/plain')
return res.status(413).send('File is too big, should be less than 5MB.')
}
}
next(err)
})
// error handlers
if (!env.isProd) {
app.use(errorHandler({ dumpExceptions: true, showStack: true }))
}
// --------------------------------------------------------------------------------------------------------------------
module.exports = app
// --------------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// local
const pkg = require('../package.json')
// ----------------------------------------------------------------------------
// setup
const isProd = process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'staging'
const isDev = !isProd
const apex = pkg.name
const protocol = process.env.PROTOCOL || 'https'
const baseUrl = `${protocol}://${apex}`
const port = process.env.PORT || 3000
const databaseUrl = process.env.DATABASE_URL
const googleAnalytics = process.env.GOOGLE_ANALYTICS
// ----------------------------------------------------------------------------
module.exports = {
apex,
protocol,
baseUrl,
port,
isProd,
isDev,
databaseUrl,
googleAnalytics,
}
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// npm
const redis = require('redis')
const rustle = require('rustle')
// local
const pkg = require('../package.json')
// ----------------------------------------------------------------------------
// redis
const client = redis.createClient()
const stats = {}
const pages = [ 'home', 'shorten', 'expand' ];
pages.forEach((name) => {
stats[name] = rustle({
client : client,
domain : pkg.name, // \
category : 'hits', // >- Keys: "<domain>:<category>:<name>"
name : name, // /
period : 24 * 60 * 60, // one day
aggregation : 'sum',
})
})
stats.pages = pages
// --------------------------------------------------------------------------------------------------------------------
module.exports = stats
// --------------------------------------------------------------------------------------------------------------------
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "pow.gd",
"title": "URL Shortener",
"description": "Online URL Shortener. Free! Simple Quick and Fast.",
"version": "0.2.0",
"private": true,
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "echo 'No tests yet!'"
},
"dependencies": {
"body-parser": "^1.18.3",
"compression": "^1.7.3",
"errorhandler": "^1.5.0",
"express": "^4.16.4",
"express-async-handler": "^1.1.4",
"logfmtr": "^1.2.1",
"pg": "^7.8.2",
"pg-patcher": "^0.4.0",
"pg-x": "^0.10.0",
"pug": "^2.0.3",
"redis": "^2.8.0",
"rustle": "^0.3.1",
"serve-favicon": "^2.5.0",
"yid": "^1.1.0",
"zid": "^0.3.0"
},
"devDependencies": {
"nodemon": "^1.18.10"
},
"author": {
"name": "Andrew Chilton",
"email": "[email protected]",
"url": "https://chilts.org/",
"twitter": "https://twitter.com/andychilton"
},
"license": "ISC"
}
-- ----------------------------------------------------------------------------
CREATE TABLE property (
key TEXT PRIMARY KEY,
value TEXT
);
INSERT INTO property(key, value) VALUES('patch', 1);
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
DROP TABLE property;
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- infrastructure
CREATE TABLE base (
inserted TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE FUNCTION updated() RETURNS trigger as '
BEGIN
NEW.updated := CURRENT_TIMESTAMP;
RETURN NEW;
END;
' LANGUAGE plpgsql;
-- sequence: object_id_seq;
CREATE SEQUENCE object_id_seq;
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- sequence: object
DROP SEQUENCE object_id_seq;
-- infrastructure
DROP FUNCTION updated();
DROP TABLE base;
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- table: url
CREATE TABLE url (
id INTEGER NOT NULL DEFAULT nextval('object_id_seq'::TEXT) PRIMARY KEY,
code TEXT NOT NULL UNIQUE,
url TEXT NOT NULL,
LIKE base INCLUDING DEFAULTS
);
CREATE TRIGGER url_update BEFORE UPDATE ON url
FOR EACH ROW EXECUTE PROCEDURE updated();
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
-- tables
DROP TABLE url;
-- ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// core
const http = require('http')
const path = require('path')
// npm
const log = require('logfmtr').default()
// local
const env = require('./lib/env.js')
const app = require('./lib/app.js')
const api = require('./lib/api.js')
// ----------------------------------------------------------------------------
// setup
log.info('started')
if (env.isDev) {
log.info(env, 'env')
}
// ----------------------------------------------------------------------------
// housekeeping
// function cleanup(callback) {
// console.log('Closing server ...')
// server.close(() => {
// console.log('Server Closed')
// console.log('Closing Database ...')
// api.close((err) => {
// if (err) {
// console.warn(err)
// return callback(err)
// }
// console.log('Database Closed')
// callback()
// })
// })
// }
function cleanup(callback) {
setTimeout(callback, 100)
}
// we'll get this from nodemon in development
process.once('SIGUSR2', () => {
log.info('SIGUSR2')
cleanup((err) => {
process.kill(process.pid, 'SIGUSR2')
})
})
process.on('SIGTERM', () => {
log.info('SIGTERM')
cleanup((err) => {
console.log('Finished')
process.exit(err ? 2 : 0)
})
})
process.on('SIGINT', () => {
log.info('SIGINT')
cleanup((err) => {
console.log('Finished')
process.exit(err ? 2 : 0)
})
})
// ----------------------------------------------------------------------------
// server
api.patch((err) => {
if (err) {
log.error(err, 'database-patch-failed')
return process.exit(2)
}
log.info('database-patched')
// server
const server = http.createServer()
server.on('request', app)
const port = env.port
server.listen(port, () => {
log.info({ port }, 'listening')
})
})
// ----------------------------------------------------------------------------
doctype html
html(lang="en")
head
meta(charset="utf-8")
meta(http-equiv="X-UA-Compatible",content="IE=edge")
meta(name="viewport",content="width=device-width, initial-scale=1.0")
meta(
name="description",
content=pkg.description)
meta(
name="keywords",
content="url, url shorten, url shortener, address shortner, url crusher, url minifier")
meta(name="author",content="Andrew Chilton, @andychilton")
link(rel="shortcut icon" href="/favicon.ico")
title= pkg.title
link(
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/0.7.2/tailwind.min.css"
integrity="sha256-krNldfVf0NG1kwbsehy7urGOekUmkcjTB+wAX+uidSI="
crossorigin="anonymous"
)
link(
rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.5.0/css/all.css"
integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU"
crossorigin="anonymous"
)
link(
href="https://fonts.googleapis.com/css?family=Raleway:500|Source+Sans+Pro"
rel="stylesheet"
)
link(
href="/s/css/main.css"
rel="stylesheet"
)
style.
a { text-decoration: none; }
body { font-family: 'Source Sans Pro', sans-serif; }
h1, h2, h3, h4, h5, h6 {
font-family: 'Raleway', sans-serif;
margin: 1em 0;
}
p { margin-bottom: 1em; }
if env === 'production' && env.googleAnalytics
// Global site tag (gtag.js) - Google Analytics
script(async="" src=`https://www.googletagmanager.com/gtag/js?id=${env.googleAnalytics}`)
script.
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '#{env.googleAnalytics}');
body
block layout
script(
src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"
integrity="sha256-FtWfRI+thWlNz2sB3SJbwKx5PgMyKIVgwHCTwa3biXc="
crossorigin="anonymous"
)
script(src="/s/js/main.js")
footer.bg-grey-lighter.p-8.mt-12.border-t.border-grey
div.flex.flex-wrap
.h-auto(class="w-full lg:w-1/4 sm:w-1/2 md:w-1/3")
.text-teal.mb-1.font-bold Dev Services and APIs
p.text-grey-dark.mb-1 Free Web Dev services and APIs.
ul.list-reset.leading-normal
li
a.text-grey-darker(class="hover:text-teal-dark" href="https://cssminifier.com/") CSS Minifier
li
a.text-grey-darker(class="hover:text-teal-dark" href="https://javascript-minifier.com") JavaScript Minifier
li
a.text-grey-darker(class="hover:text-teal-dark" href="https://html-minifier.com/") HTML Minifier
li
a.text-grey-darker(class="hover:text-teal-dark" href="https://pngcrush.com/") PNG Crush
li
a.text-grey-darker(class="hover:text-teal-dark" href="https://jpgoptimiser.com") JPG Optimiser
li
a.text-grey-darker(class="hover:text-teal-dark" href="https://img-resize.com/") Image Resize
li
a.text-grey-darker(class="hover:text-teal-dark" href="https://postb.in") PostBin
li
a.text-grey-darker(class="hover:text-teal-dark" href="https://bcrypt.org") Bcrypt Test API
li
a.text-grey-darker(class="hover:text-teal-dark" href="https://paste.gd") Paste Service
li
a.text-grey-darker(class="hover:text-teal-dark" href="https://pow.gd") URL Shortener
li
a.text-grey-darker(class="hover:text-teal-dark" href="https://markdown.gd") Publish Markdown Articles
li
a.text-grey-darker(class="hover:text-teal-dark" href="https://feed2json.org/") Feed 2 JSON
.h-auto(class="w-full lg:w-1/4 sm:w-1/2 md:w-1/3")
.text-blue.mb-1.font-bold PageCSS
p.text-grey-dark.mb-1 Free HTML and CSS web designs.
p.text-grey-dark.mb-1 Coming soon!
// ul.list-reset.leading-normal
li
a.text-grey-darker(class="hover:text-blue-dark" href="#") Starlight
li
a.text-grey-darker(class="hover:text-blue-dark" href="#") Astro
li
a.text-grey-darker(class="hover:text-blue-dark" href="#") Chromatic
li
a.text-grey-darker(class="hover:text-blue-dark" href="#") Lithograph
li
a.text-grey-darker(class="hover:text-blue-dark" href="#") Typewriter
li
a.text-grey-darker(class="hover:text-blue-dark" href="#") Flight
// .h-auto(class="w-full lg:w-1/4 sm:w-1/2 md:w-1/3")
.text-purple.mb-1.font-bold Curated Press
p.text-grey-dark.mb-1 Curated news and links to technical articles.
ul.list-reset.leading-normal
li
a.text-grey-darker(class="hover:text-purple-dark" href="https://curated.press/go") Curated Go
li
a.text-grey-darker(class="hover:text-purple-dark" href="https://curated.press/javascript") Curated JavaScript
li
a.text-grey-darker(class="hover:text-purple-dark" href="https://curated.press/postgres") Curated Postgres
li
a.text-grey-darker(class="hover:text-purple-dark" href="https://curated.press/perl") Curated Perl
li
a.text-grey-darker(class="hover:text-purple-dark" href="https://curated.press/php") Curated PHP
li
a.text-grey-darker(class="hover:text-purple-dark" href="https://curated.press/mysql") Curated MySQL
li
a.text-grey-darker(class="hover:text-purple-dark" href="https://curated.press/redis") Curated Redis
li
a.text-grey-darker(class="hover:text-purple-dark" href="https://curated.press/java") Curated Java
li
a.text-grey-darker(class="hover:text-purple-dark" href="https://curated.press/linux") Curated Linux
li
a.text-grey-darker(class="hover:text-purple-dark" href="https://curated.press/react") Curated React
li
a.text-grey-darker(class="hover:text-purple-dark" href="https://curated.press/vue") Curated Vue.js
li
a.text-grey-darker(class="hover:text-purple-dark" href="https://curated.press/startup") Curated Startup
.h-auto(class="w-full lg:w-1/4 sm:w-1/2 md:w-1/3")
.text-green-dark.mb-1.font-bold Andy Chilton
p.text-grey-dark.mb-1 Developer, Node.js, Postgres, Redis.
p.text-grey-darker
a.text-grey-darker.border.border-grey-darker.fab.fa-twitter.inline-block.rounded-full.p-2.my-2.mr-2(class="hover:text-green-dark" href="https://twitter.com/andychilton")
a.text-grey-darker.border.border-grey-darker.fab.fa-gitlab.inline-block.rounded-full.p-2.m-2(class="hover:text-green-dark" href="https://gitlab.com/chilts")
a.text-grey-darker.border.border-grey-darker.fas.fa-globe.inline-block.rounded-full.p-2.m-2(class="hover:text-green-dark hover:border-text-green-dark" href="https://chilts.org/")
.text-green-dark.mb-1.font-bold WebDevSH
p.text-grey-dark.mb-1 Web Dev Utilities and Tools.
p.text-grey-darker
a.text-grey-darker.border.border-grey-darker.fab.fa-twitter.inline-block.rounded-full.p-2.my-2.mr-2(class="hover:text-green-dark" href="https://twitter.com/WebDevSH")
a.text-grey-darker.border.border-grey-darker.fab.fa-gitlab.inline-block.rounded-full.p-2.m-2(class="hover:text-green-dark" href="https://gitlab.com/webdev.sh")
a.text-grey-darker.border.border-grey-darker.fas.fa-globe.inline-block.rounded-full.p-2.m-2(class="hover:text-green-dark hover:border-text-green-dark" href="https://webdev.sh/")
.text-green-dark.mb-1.font-bold Apps Attic
p.text-grey-dark.mb-1 Open Source Web Apps.
p.text-grey-darker
a.text-grey-darker.border.border-grey-darker.fab.fa-twitter.inline-block.rounded-full.p-2.my-2.mr-2(class="hover:text-green-dark" href="https://twitter.com/AppsAttic")
a.text-grey-darker.border.border-grey-darker.fab.fa-gitlab.inline-block.rounded-full.p-2.m-2(class="hover:text-green-dark" href="https://gitlab.com/appsattic")
a.text-grey-darker.border.border-grey-darker.fas.fa-globe.inline-block.rounded-full.p-2.m-2(class="hover:text-green-dark hover:border-text-green-dark" href="https://appsattic.com/")
// .h-auto(class="w-full lg:w-1/4 sm:w-1/2 md:w-1/3")
.text-red-light.mb-1.font-bold Newsletter
p.text-grey-darker.leading-normal Sign up for our mailing list, full of hints, tips, new services and new designs.
.mt-4.flex
input.p-2.border.border-grey-light.round.text-grey-dark.text-sm.h-auto(type="text" placeholder="Your email address")
button.bg-teal.text-white.rounded-sm.h-auto.text-xs.p-3 Subscribe
extends layout
block content
.bg-grey-lighter.text-center.m-12.p-12.max-w-md.mx-auto
h1 Pow!
h2 Shorten your URLs
p This service is free to use.
form(action="/" method="post")
input.p-6.m-3.border.border-grey-dark.rounded(name="url" placeholder="https://...")
input.bg-blue-dark.text-white.p-6.m-3.rounded(type="submit" value="Shorten")
extends base
block layout
include nav
#app.container.mx-auto
block content
include footer
nav.flex.items-center.justify-between.flex-wrap.bg-teal.p-6
.flex.items-center.flex-no-shrink.text-white.mr-6
span.text-3xl.mx-2.fa.fa-globe
span.font-semibold.text-xl.tracking-tight= pkg.title
.block(class="lg:hidden")
button.flex.items-center.px-3.py-2.border.rounded.text-teal-lighter.border-teal-light(class="hover:text-white hover:border-white")
svg.fill-current.h-3.w-3(viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg")
title Menu
path(d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z")
.w-full.block.flex-grow(class="lg:flex lg:items-center lg:w-auto")
div(class="lg:flex-grow")
a.block.mt-4.text-white.mr-4(href="/" class="lg:inline-block lg:mt-0 hover:text-teal-darker")
| Docs
a.block.mt-4.text-white.mr-4(href="/examples" class="lg:inline-block lg:mt-0 hover:text-teal-darker")
| Examples
a.block.mt-4.text-white(href="https://webdev.sh/blog/" target="_blank" class="lg:inline-block lg:mt-0 hover:text-teal-darker")
| Blog
div
a.block.mt-4.text-white.mr-4.fab.fa-gitlab(href=`https://gitlab.com/webdev.sh/${pkg.name}` class="lg:inline-block lg:mt-0 hover:text-teal-darker")
a.block.mt-4.text-white.mr-4.fab.fa-twitter(href="https://twitter.com/WebDevSH" class="lg:inline-block lg:mt-0 hover:text-teal-darker")
a.block.mt-4.text-white.fas.fa-globe(href="https://webdev.sh" class="lg:inline-block lg:mt-0 hover:text-teal-darker")
extends layout
block content
h3.muted Stats
table.table.table-striped(style="table-layout: fixed; width: 50%;")
thead
tr
th Day
th.r Home
th.r Shorten
th.r Expand
tbody
for ts in Object.keys(stats).sort()
tr
td= (new Date(ts * 1000)).toISOString()
td.r= stats[ts].home || 0
td.r= stats[ts].shorten || 0
td.r= stats[ts].expand || 0
p (Ends)
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