Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jstayton committed Oct 1, 2020
0 parents commit 6017b27
Show file tree
Hide file tree
Showing 108 changed files with 14,378 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .env.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
NODE_ENV=development

BUGSNAG_API_KEY=
DATABASE_URL=postgres://postgres:password@localhost:5432/piratepx_development
PORT=3000
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/web
16 changes: 16 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"root": true,
"env": {
"node": true,
"es2020": true
},
"extends": ["eslint:recommended", "prettier"],
"settings": {
"import/resolver": {
"alias": [["@", "api"]]
}
},
"rules": {
"require-atomic-updates": "off"
}
}
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# npm
/node_modules

# dotenv
.env

# Logs
npm-debug.log*
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
14
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/web
5 changes: 5 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"proseWrap": "always",
"semi": false,
"singleQuote": true
}
19 changes: 19 additions & 0 deletions .release-it.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"git": {
"commitMessage": "Release v${version}",
"tagName": "v${version}",
"tagAnnotation": "Release v${version}"
},
"github": {
"release": true,
"releaseName": "Release v${version}"
},
"npm": {
"publish": false
},
"plugins": {
"@release-it/bumper": {
"out": ["web/package.json", "web/package-lock.json"]
}
}
}
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2020 piratepx

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
134 changes: 134 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# 🏴‍☠️px App

A simple, privacy-respecting, no cookie, zero JavaScript, 35 byte counter pixel
for websites, mobile apps, server-side APIs, CLIs, and just about anywhere else.

Sign up for free at https://www.piratepx.com!

## Overview

This repository contains both the backend and frontend of the app to simplify
development and deployment. Other than a few lines of configuration here and
there to make this possible, however, they're pretty much separate codebases.

### Backend

The backend is a JSON REST API built in [Node.js](https://nodejs.org/) using
[Fastify](https://www.fastify.io/) and
[Objection.js](https://vincit.github.io/objection.js/). It persists data to a
[PostgreSQL](https://www.postgresql.org/) database.

The source code is located in the [`api`](api) directory, with configuration
files in the root of this repository (where this README lives).

### Frontend

The frontend is a single-page app built with [Vue.js](https://vuejs.org/) and
[Tailwind CSS](https://tailwindcss.com/).

The source code is fully isolated in the [`web`](web) directory, which is also
where its own configuration files are located.

## Development

The following includes the necessary steps to get the full app setup for
development, with a focus on backend-specific details. See
[`web/README.md`](web/README.md) for frontend-specific details.

### Prerequisites

- [Node.js](https://nodejs.org/) (see `engines.node` in
[`package.json`](package.json))
- [PostgreSQL](https://www.postgresql.org/) >= v11

[Docker Compose](https://docs.docker.com/compose/) is used to run PostgreSQL as
configured in [`docker-compose.yml`](docker-compose.yml). Once installed, simply
run:

```bash
$ docker-compose up
```

The app itself is not run in a Docker container in development, as it's easy
enough to install the necessary version of Node.js with
[nvm](https://github.com/nvm-sh/nvm):

```bash
$ nvm install
```

### Dependencies

Install dependencies with npm:

```bash
$ npm install
$ cd web && npm install
```

### Config

[dotenv](https://github.com/motdotla/dotenv) is used to load environment
variables from a `.env` file into `process.env`. This file is ignored by version
control to prevent committing secrets.

See [`.env.dist`](.env.dist) for an example.

### Database

#### Create

Ensure PostgreSQL is running, then:

```bash
$ npm run dev:db:create
```

#### Migrations

[Knex.js](https://knexjs.org/#Migrations) is used to manage database migrations,
which are located in [`api/db/migrations`](api/db/migrations).

To run the latest migrations:

```bash
$ npm run knex migrate:latest
```

### Start

Start both the backend and frontend development servers:

```bash
$ npm run dev
```

### Code Style & Linting

[Prettier](https://prettier.com/) is setup to enforce a consistent code style.
It's highly recommended to
[add an integration to your editor](https://prettier.io/docs/en/editors.html)
that automatically formats on save.

[ESLint](https://eslint.org/) is setup with the
["recommended" rules](https://eslint.org/docs/rules/) to enforce a level of code
quality. It's also highly recommended to
[add an integration to your editor](https://eslint.org/docs/user-guide/integrations#editors)
that automatically formats on save.

To run via the command line:

```bash
$ npm run lint
```

## Releasing

After development is done in the `development` branch and is ready for release,
it should be merged into the `master` branch, where the latest release code
lives. [Release It!](https://github.com/release-it/release-it) is then used to
interactively orchestrate the release process:

```bash
$ npm run release
```
40 changes: 40 additions & 0 deletions api/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const path = require('path')
const zlib = require('zlib')

const AutoLoad = require('fastify-autoload')

const initializers = require('./initializers')

require('@/services/bugsnag')

const errorHandler = require('@/plugins/error_handler')
const scheduleCronJobs = require('@/services/schedule_cron_jobs')

module.exports = async (fastify, opts) => {
await initializers()

fastify.register(require('fastify-compress'), {
zlibOptions: {
level: zlib.constants.Z_DEFAULT_COMPRESSION,
},
})

fastify.register(require('fastify-static'), {
root: path.join(__dirname, '../web/dist'),
wildcard: false,
})

fastify.register(AutoLoad, {
dir: path.join(__dirname, 'plugins', 'common'),
options: { ...opts },
})

fastify.register(AutoLoad, {
dir: path.join(__dirname, 'routes'),
options: { ...opts },
})

fastify.setErrorHandler(errorHandler)

scheduleCronJobs()
}
14 changes: 14 additions & 0 deletions api/db/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const defaults = {
client: 'pg',
connection: process.env.DATABASE_URL,
}

module.exports = {
development: {
...defaults,
debug: true,
},
production: {
...defaults,
},
}
5 changes: 5 additions & 0 deletions api/db/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const Knex = require('knex')

const config = require('@/db/config')

module.exports = Knex(config[process.env.NODE_ENV])
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
exports.up = (knex) => knex.raw('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"')

exports.down = (knex) => knex.raw('DROP EXTENSION IF EXISTS "uuid-ossp"')
12 changes: 12 additions & 0 deletions api/db/migrations/20200713072746_create_users.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
exports.up = (knex) =>
knex.schema.createTable('users', (t) => {
t.uuid('id')
.defaultTo(knex.raw('uuid_generate_v4()'))
.notNullable()
.primary()
t.string('email').notNullable().unique()
t.datetime('created_at').notNullable()
t.datetime('updated_at').notNullable()
})

exports.down = (knex) => knex.schema.dropTable('users')
15 changes: 15 additions & 0 deletions api/db/migrations/20200713073113_create_projects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
exports.up = (knex) =>
knex.schema.createTable('projects', (t) => {
t.uuid('id')
.defaultTo(knex.raw('uuid_generate_v4()'))
.notNullable()
.primary()
t.uuid('user_id').notNullable().references('users.id').index()
t.string('name').notNullable()
t.string('time_zone').notNullable()
t.string('secret').notNullable().unique()
t.datetime('created_at').notNullable()
t.datetime('updated_at').notNullable()
})

exports.down = (knex) => knex.schema.dropTable('projects')
17 changes: 17 additions & 0 deletions api/db/migrations/20200713073310_create_counts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
exports.up = (knex) =>
knex.schema.createTable('counts', (t) => {
t.uuid('id')
.defaultTo(knex.raw('uuid_generate_v4()'))
.notNullable()
.primary()
t.uuid('project_id').notNullable().references('projects.id')
t.string('identifier').notNullable()
t.date('date').notNullable().index()
t.integer('count').notNullable().defaultTo(0)
t.datetime('created_at').notNullable()
t.datetime('updated_at').notNullable()

t.unique(['project_id', 'identifier', 'date'])
})

exports.down = (knex) => knex.schema.dropTable('counts')
10 changes: 10 additions & 0 deletions api/initializers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require('dotenv').config()
require('module-alias/register')

const objection = require('@/initializers/objection')
const pg = require('@/initializers/pg')

module.exports = async () => {
pg()
await objection()
}
9 changes: 9 additions & 0 deletions api/initializers/objection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const { Model } = require('objection')

const db = require('@/db')

module.exports = () => {
Model.knex(db)

return db.raw("SELECT 'Hello?'")
}
7 changes: 7 additions & 0 deletions api/initializers/pg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { types } = require('pg')

module.exports = () => {
// Parse date type as string instead of Date to prevent time zone conversion.
// See: https://github.com/brianc/node-postgres/issues/1844
types.setTypeParser(1082, (date) => date)
}
11 changes: 11 additions & 0 deletions api/lib/errors/app_error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const BaseError = require('@/lib/errors/base_error')

class AppError extends BaseError {
constructor(status, message) {
super(message)

this.status = status
}
}

module.exports = AppError
11 changes: 11 additions & 0 deletions api/lib/errors/base_error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class BaseError extends Error {
constructor(message) {
super(message)

Error.captureStackTrace(this, this.constructor)

this.name = this.constructor.name
}
}

module.exports = BaseError
5 changes: 5 additions & 0 deletions api/lib/errors/not_implemented_error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const BaseError = require('@/lib/errors/base_error')

class NotImplementedError extends BaseError {}

module.exports = NotImplementedError
Loading

0 comments on commit 6017b27

Please sign in to comment.