Skip to content
Closed
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
12 changes: 5 additions & 7 deletions src/NotificationManager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import io from '@pm2/io'
import admin from 'firebase-admin'

import { ApiKey } from './models'
import { ApiKey } from './types/pushTypes'

import BatchResponse = admin.messaging.BatchResponse

Expand All @@ -17,19 +17,17 @@ const failureCounter = io.counter({
export class NotificationManager {
private constructor(private readonly app: admin.app.App) {}

public static async init(
apiKey: ApiKey | string
): Promise<NotificationManager> {
if (typeof apiKey === 'string') apiKey = await ApiKey.fetch(apiKey)

public static async init(apiKey: ApiKey): Promise<NotificationManager> {
const name = `app:${apiKey.appId}`
let app: admin.app.App
try {
app = admin.app(name)
} catch (err) {
app = admin.initializeApp(
{
credential: admin.credential.cert(apiKey.adminsdk)
// TODO: We have never passed the correct data type here,
// so either update our database or write a translation layer:
credential: admin.credential.cert(apiKey.adminsdk as any)
},
name
)
Expand Down
60 changes: 60 additions & 0 deletions src/db/couchApiKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { asBoolean, asObject, asOptional, asString, Cleaner } from 'cleaners'
import {
asCouchDoc,
asMaybeNotFoundError,
DatabaseSetup
} from 'edge-server-tools'
import { ServerScope } from 'nano'

import { ApiKey, FirebaseAdminKey } from '../types/pushTypes'

export const asFirebaseAdminKey: Cleaner<FirebaseAdminKey> = asObject({
type: asOptional(asString),
project_id: asOptional(asString),

auth_provider_x509_cert_url: asOptional(asString),
auth_uri: asOptional(asString),
client_email: asOptional(asString),
client_id: asOptional(asString),
client_x509_cert_url: asOptional(asString),
private_key_id: asOptional(asString),
private_key: asOptional(asString),
token_uri: asOptional(asString)
}).withRest

/**
* An API key, as stored in Couch.
*/
export const asCouchApiKey = asCouchDoc<Omit<ApiKey, 'apiKey'>>(
asObject({
appId: asString,
admin: asBoolean,
adminsdk: asOptional(asFirebaseAdminKey)
})
)
type CouchApiKey = ReturnType<typeof asCouchApiKey>

/**
* The document key is the api key.
*/
export const couchApiKeysSetup: DatabaseSetup = {
name: 'db_api_keys'
}

export async function getApiKeyByKey(
connection: ServerScope,
apiKey: string
): Promise<ApiKey | undefined> {
const db = connection.db.use(couchApiKeysSetup.name)
const raw = await db.get(apiKey).catch(error => {
if (asMaybeNotFoundError(error) != null) return
throw error
})

if (raw == null) return
return unpackApiKey(asCouchApiKey(raw))
}

function unpackApiKey(doc: CouchApiKey): ApiKey {
return { ...doc.doc, apiKey: doc.id }
}
12 changes: 10 additions & 2 deletions src/db/couchSettings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { asArray, asMaybe, asNumber, asObject, asString } from 'cleaners'
import { asArray, asBoolean, asMaybe, asNumber, asObject, asString } from 'cleaners'
import {
asReplicatorSetupDocument,
DatabaseSetup,
Expand All @@ -21,14 +21,22 @@ const asSettings = asObject({
priceCheckInMinutes: asMaybe(asNumber, 5)
})

const asDefaultThresholds = asObject({
anomaly: asBoolean
})

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

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

export const settingsSetup: DatabaseSetup = {
name: 'push-settings',
syncedDocuments: [syncedReplicators, syncedSettings]
syncedDocuments: [syncedReplicators, syncedSettings, syncedDefaultThresholds]
}
42 changes: 42 additions & 0 deletions src/db/couchUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
asArray,
asBoolean,
asMap,
asMaybe,
asObject,
asString
} from 'cleaners'
import { asCouchDoc } from 'edge-server-tools'
import { domainToASCII } from 'url'

import { User } from '../types/pushTypes'

const asCouchUser = asCouchDoc<Omit<User, 'loginId'>>(
asObject({
devices: asObject(asBoolean),
notifications: asObject({
enabled: asBoolean,
currencyCodes: asObject(
asObject({
'1': asBoolean,
'24': asBoolean
})
)
})
})
)
type CouchUser = ReturnType<typeof asCouchUser>

export async function getUserById(
connection: ServerScope,
id: string
): Promise<User> {}

export async function updateUserById(
connection: ServerScope,
user: User
): Promise<User> {}

function unpackUser(doc: CouchUser): User {
return { ...doc.doc, loginId: doc.id, notifications: Object.keys(doc.notifications.currencyCodes).map(key => ...arguments.FutureNotifType) }
}
23 changes: 0 additions & 23 deletions src/models/ApiKey.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/models/CurrencyThreshold.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { asBoolean, asMap, asNumber, asObject, asOptional } from 'cleaners'
import Nano from 'nano'

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

const nanoDb = Nano(serverConfig.couchUri)
Expand Down
2 changes: 1 addition & 1 deletion src/models/Defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { asMap } from 'cleaners'
import Nano from 'nano'

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

const nanoDb = Nano(serverConfig.couchUri)
const dbCurrencyThreshold = nanoDb.db.use('defaults')
Expand Down
2 changes: 1 addition & 1 deletion src/models/Device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { asNumber, asObject, asOptional, asString } from 'cleaners'
import Nano from 'nano'

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

const nanoDb = Nano(serverConfig.couchUri)
const dbDevices = nanoDb.db.use<ReturnType<typeof asDevice>>('db_devices')
Expand Down
2 changes: 1 addition & 1 deletion src/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { asBoolean, asMap, asObject, asOptional } from 'cleaners'
import Nano from 'nano'

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

const nanoDb = Nano(serverConfig.couchUri)
Expand Down
5 changes: 0 additions & 5 deletions src/models/index.ts

This file was deleted.

4 changes: 3 additions & 1 deletion src/price-script/checkPriceChanges.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import io from '@pm2/io'
import { MetricType } from '@pm2/io/build/main/services/metrics'

import { CurrencyThreshold, Device, User } from '../models'
import { CurrencyThreshold } from '../models/CurrencyThreshold'
import { Device } from '../models/Device'
import { User } from '../models/User'
import { NotificationManager } from '../NotificationManager'
import { fetchThresholdPrice } from './fetchThresholdPrices'

Expand Down
2 changes: 1 addition & 1 deletion src/price-script/fetchThresholdPrices.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import io from '@pm2/io'

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

Expand Down
11 changes: 8 additions & 3 deletions src/price-script/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import io from '@pm2/io'
import { makePeriodicTask } from 'edge-server-tools'
import nano from 'nano'

import { getApiKeyByKey } from '../db/couchApiKeys'
import { syncedSettings } from '../db/couchSettings'
import { setupDatabases } from '../db/couchSetup'
import { NotificationManager } from '../NotificationManager'
Expand All @@ -22,9 +23,13 @@ async function main(): Promise<void> {

// Read the API keys from settings:
const managers = await Promise.all(
syncedSettings.doc.apiKeys.map(
async partner => await NotificationManager.init(partner.apiKey)
)
syncedSettings.doc.apiKeys.map(async partner => {
const apiKey = await getApiKeyByKey(nano(couchUri), partner.apiKey)
if (apiKey == null) {
throw new Error(`Cannot find API key ${partner.apiKey}`)
}
return await NotificationManager.init(apiKey)
})
)

// Check the prices every few minutes:
Expand Down
9 changes: 6 additions & 3 deletions src/server/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import io from '@pm2/io'
import bodyParser from 'body-parser'
import cors from 'cors'
import express from 'express'
import nano from 'nano'

import { ApiKey } from '../models'
import { getApiKeyByKey } from '../db/couchApiKeys'
import { serverConfig } from '../serverConfig'
import { ApiKey } from '../types/pushTypes'
import { DeviceController } from './controllers/DeviceController'
import { NotificationController } from './controllers/NotificationController'
import { UserController } from './controllers/UserController'
Expand Down Expand Up @@ -46,8 +49,8 @@ router.use(async (req, res, next) => {
const apiKey = req.header('X-Api-Key')
if (!apiKey) return res.sendStatus(401)

const key = await ApiKey.fetch(apiKey)
if (!key) return res.sendStatus(401)
const key = await getApiKeyByKey(nano(serverConfig.couchUri), apiKey)
if (key == null) return res.sendStatus(401)

req.apiKey = key

Expand Down
2 changes: 1 addition & 1 deletion src/server/controllers/DeviceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { asObject, asString } from 'cleaners'
import express from 'express'

import { Device } from '../../models'
import { Device } from '../../models/Device'

export const DeviceController = express.Router()

Expand Down
2 changes: 1 addition & 1 deletion src/server/controllers/NotificationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { asMap, asObject, asOptional, asString, asUnknown } from 'cleaners'
import express from 'express'

import { User } from '../../models'
import { User } from '../../models/User'
import { NotificationManager } from '../../NotificationManager'

export const NotificationController = express.Router()
Expand Down
2 changes: 1 addition & 1 deletion src/server/controllers/UserController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { asArray, asBoolean, asObject, asString } from 'cleaners'
import express from 'express'

import { User } from '../../models'
import { User } from '../../models/User'

export const UserController = express.Router()

Expand Down
Loading