Skip to content

Commit

Permalink
Merge branch 'william/project-structure'
Browse files Browse the repository at this point in the history
  • Loading branch information
swansontec committed Jun 17, 2022
2 parents ed5dc27 + de85977 commit efeacdb
Show file tree
Hide file tree
Showing 26 changed files with 594 additions and 233 deletions.
21 changes: 15 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
.idea
node_modules
# Build output:
/lib/
/logs/
/serverConfig.json
/pushServerConfig.json

build
serverConfig.json
tsconfig.tsbuildinfo
logs
# Package managers:
node_modules/
npm-debug.log
package-lock.json
yarn-error.log

# Editors:
.DS_Store
.idea/
.vscode/
55 changes: 54 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,54 @@
edge-notifications
# edge-push-server

This server sends push notifications to Edge client apps. It contains an HTTP server that clients can use to register for notifications, and a background process that checks for price changes and actually sends the messages.

## Setup

This server requires a working copies of Node.js, Yarn, PM2, and CouchDB. We also recommend using Caddy to terminate SSL connections.

### Set up logging

Run these commands as a server admin:

```sh
touch /var/log/pushServer.log
touch /var/log/priceDaemon.log
chown edgy /var/log/pushServer.log /var/log/priceDaemon.log
cp ./docs/logrotate /etc/logrotate.d/pushServer
```

### Manage server using `pm2`

First, tell pm2 how to run the server script:

```sh
# install:
pm2 start pm2.json
pm2 save

# check status:
pm2 monit
tail -f /var/log/pushServer.log
tail -f /var/log/priceDaemon.log

# manage:
pm2 reload pm2.json
pm2 restart pm2.json
pm2 stop pm2.json

pm2 restart pushServer // Just the HTTP server
pm2 restart priceDaemon // Just the price checker
```

### Updating

To update the code running on the production server, use the following procedure:

```sh
git pull
yarn
yarn prepare
pm2 restart pm2.json
```

Each deployment should come with its own version bump, changelog update, and git tag.
17 changes: 17 additions & 0 deletions docs/logrotate
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# /etc/logrotate.d/pushServer

/var/log/pushServer.log {
copytruncate
daily
missingok
notifempty
rotate 10
}

/var/log/priceDaemon.log {
copytruncate
daily
missingok
notifempty
rotate 10
}
28 changes: 15 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "edge-push-notifications",
"name": "edge-push-server",
"version": "1.0.0",
"private": true,
"description": "",
Expand All @@ -8,15 +8,15 @@
"author": "",
"main": "build/index.js",
"scripts": {
"compile": "tsc",
"fix": "npm run lint -- --fix",
"lint": "eslint --ext .js,.ts .",
"precommit": "lint-staged && tsc",
"prepare": "husky install",
"price:script:pm2": "pm2 start build/price-script/index.js --name price-script --log logs/price-script.log --time",
"price:script": "node build/index.js",
"server:pm2": "pm2 start build/server/index.js --name server --log logs/server.log --time",
"server": "node build/server.js"
"build": "sucrase -q -t typescript,imports -d ./lib ./src",
"clean": "rimraf lib",
"fix": "eslint . --fix",
"lint": "eslint .",
"precommit": "lint-staged && npm-run-all types prepare",
"prepare": "husky install && npm-run-all clean build",
"start-server": "node -r sucrase/register src/server/index.ts",
"start-price": "node -r sucrase/register src/price-script/index.ts",
"types": "tsc"
},
"lint-staged": {
"*.{js,ts}": "eslint"
Expand All @@ -25,12 +25,13 @@
"@pm2/io": "^4.3.5",
"axios": "^0.21.2",
"body-parser": "^1.19.0",
"cleaner-config": "^0.1.7",
"cleaners": "^0.3.12",
"cors": "^2.8.5",
"edge-server-tools": "^0.2.11",
"express": "^4.17.1",
"firebase-admin": "^8.12.1",
"nano": "^9.0.5",
"node-schedule": "^1.3.2"
"nano": "^9.0.5"
},
"devDependencies": {
"@types/cors": "^2.8.7",
Expand All @@ -49,8 +50,9 @@
"eslint-plugin-standard": "^4.0.1",
"husky": "^7.0.0",
"lint-staged": "^10.4.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.1.2",
"ts-node": "^9.0.0",
"sucrase": "^3.21.0",
"typescript": "^4.7.3"
}
}
14 changes: 14 additions & 0 deletions pm2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"apps": [
{
"name": "pushServer",
"script": "./lib/server/index.js",
"out_file": "/var/log/pushServer.log"
},
{
"name": "priceDaemon",
"script": "./lib/price-script/index.js",
"out_file": "/var/log/priceDaemon.log"
}
]
}
12 changes: 0 additions & 12 deletions serverConfig.json.sample

This file was deleted.

4 changes: 2 additions & 2 deletions src/NotificationManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as io from '@pm2/io'
import * as admin from 'firebase-admin'
import io from '@pm2/io'
import admin from 'firebase-admin'

import { ApiKey } from './models'

Expand Down
91 changes: 91 additions & 0 deletions src/couchSetup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { asArray, asMaybe, asNumber, asObject, asString } from 'cleaners'
import {
asReplicatorSetupDocument,
DatabaseSetup,
setupDatabase,
SetupDatabaseOptions,
syncedDocument
} from 'edge-server-tools'
import { ServerScope } from 'nano'

import { serverConfig } from './serverConfig'

// ---------------------------------------------------------------------------
// Synced documents
// ---------------------------------------------------------------------------

/**
* Live-updating server options stored in the `push-settings` database.
*/
const asSettings = asObject({
apiKeys: asMaybe(
asArray(
asObject({
name: asString,
apiKey: asString
})
),
[]
),
priceCheckInMinutes: asMaybe(asNumber, 5)
})

export const syncedReplicators = syncedDocument(
'replicators',
asReplicatorSetupDocument
)

export const syncedSettings = syncedDocument('settings', asSettings.withRest)

// ---------------------------------------------------------------------------
// Databases
// ---------------------------------------------------------------------------

export const settingsSetup: DatabaseSetup = {
name: 'push-settings',
syncedDocuments: [syncedReplicators, syncedSettings]
}

const apiKeysSetup: DatabaseSetup = { name: 'db_api_keys' }

const thresholdsSetup: DatabaseSetup = { name: 'db_currency_thresholds' }

const devicesSetup: DatabaseSetup = { name: 'db_devices' }

const usersSetup: DatabaseSetup = {
name: 'db_user_settings'
// documents: {
// '_design/filter': makeJsDesign('by-currency', ?),
// '_design/map': makeJsDesign('currency-codes', ?)
// }
}

const defaultsSetup: DatabaseSetup = {
name: 'defaults'
// syncedDocuments: ['thresholds']
}

// ---------------------------------------------------------------------------
// Setup routine
// ---------------------------------------------------------------------------

export async function setupDatabases(
connection: ServerScope,
disableWatching: boolean = false
): Promise<void> {
const { currentCluster } = serverConfig
const options: SetupDatabaseOptions = {
currentCluster,
replicatorSetup: syncedReplicators,
disableWatching
}

await setupDatabase(connection, settingsSetup, options)
await Promise.all([
setupDatabase(connection, apiKeysSetup, options),
setupDatabase(connection, thresholdsSetup, options),
setupDatabase(connection, devicesSetup, options),
setupDatabase(connection, usersSetup, options),
setupDatabase(connection, defaultsSetup, options)
])
}
8 changes: 3 additions & 5 deletions src/models/ApiKey.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { asBoolean, asMap, asObject, asOptional, asString } from 'cleaners'
import * as Nano from 'nano'
import Nano from 'nano'

import { serverConfig } from '../serverConfig'
import { Base } from '.'

// eslint-disable-next-line @typescript-eslint/no-var-requires
const CONFIG = require('../../serverConfig.json')

const nanoDb = Nano(CONFIG.dbFullpath)
const nanoDb = Nano(serverConfig.couchUri)
const dbDevices = nanoDb.db.use('db_api_keys')

const asApiKey = asObject({
Expand Down
8 changes: 3 additions & 5 deletions src/models/CurrencyThreshold.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { asBoolean, asMap, asNumber, asObject, asOptional } from 'cleaners'
import * as Nano from 'nano'
import Nano from 'nano'

import { serverConfig } from '../serverConfig'
import { Base } from '.'
import { Defaults } from './Defaults'

// eslint-disable-next-line @typescript-eslint/no-var-requires
const CONFIG = require('../../serverConfig.json')

const nanoDb = Nano(CONFIG.dbFullpath)
const nanoDb = Nano(serverConfig.couchUri)
const dbCurrencyThreshold = nanoDb.db.use('db_currency_thresholds')

const asThreshold = asObject({
Expand Down
8 changes: 3 additions & 5 deletions src/models/Defaults.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { asMap } from 'cleaners'
import * as Nano from 'nano'
import Nano from 'nano'

import { serverConfig } from '../serverConfig'
import { Base } from '.'

// eslint-disable-next-line @typescript-eslint/no-var-requires
const CONFIG = require('../../serverConfig.json')

const nanoDb = Nano(CONFIG.dbFullpath)
const nanoDb = Nano(serverConfig.couchUri)
const dbCurrencyThreshold = nanoDb.db.use('defaults')

export class Defaults extends Base {
Expand Down
8 changes: 3 additions & 5 deletions src/models/Device.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { asNumber, asObject, asOptional, asString } from 'cleaners'
import * as Nano from 'nano'
import Nano from 'nano'

import { serverConfig } from '../serverConfig'
import { Base } from '.'

// eslint-disable-next-line @typescript-eslint/no-var-requires
const CONFIG = require('../../serverConfig.json')

const nanoDb = Nano(CONFIG.dbFullpath)
const nanoDb = Nano(serverConfig.couchUri)
const dbDevices = nanoDb.db.use<ReturnType<typeof asDevice>>('db_devices')

const asDevice = asObject({
Expand Down
8 changes: 3 additions & 5 deletions src/models/User.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { asBoolean, asMap, asObject, asOptional } from 'cleaners'
import * as Nano from 'nano'
import Nano from 'nano'

import { serverConfig } from '../serverConfig'
import { Base } from '.'
import { Device } from './Device'

// eslint-disable-next-line @typescript-eslint/no-var-requires
const CONFIG = require('../../serverConfig.json')

const nanoDb = Nano(CONFIG.dbFullpath)
const nanoDb = Nano(serverConfig.couchUri)
const dbUserSettings =
nanoDb.db.use<ReturnType<typeof asUser>>('db_user_settings')

Expand Down
2 changes: 1 addition & 1 deletion src/models/base.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { asObject, Cleaner } from 'cleaners'
import * as Nano from 'nano'
import Nano from 'nano'

const asModelData = asObject<Nano.MaybeDocument>({})

Expand Down
2 changes: 1 addition & 1 deletion src/price-script/checkPriceChanges.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as io from '@pm2/io'
import io from '@pm2/io'
import { MetricType } from '@pm2/io/build/main/services/metrics'

import { CurrencyThreshold, Device, User } from '../models'
Expand Down
4 changes: 2 additions & 2 deletions src/price-script/fetchThresholdPrices.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as io from '@pm2/io'
import Counter from '@pm2/io/build/main/utils/metrics/counter'
import io from '@pm2/io'

import { CurrencyThreshold } from '../models'
import { NotificationPriceChange } from './checkPriceChanges'
import { getPrice } from './prices'

const SLEEP_TIMEOUT = 1000 // in milliseconds

type Counter = ReturnType<typeof io.counter>
const processMetrics: { [id: string]: Counter | undefined } = {}

async function sleep(ms = SLEEP_TIMEOUT) {
Expand Down
Loading

0 comments on commit efeacdb

Please sign in to comment.