mirror of
https://github.com/chuot/rdio-scanner.git
synced 2026-08-16 01:43:01 -06:00
Version 3.1
This commit is contained in:
parent
0b653aaba8
commit
db7b464b99
|
|
@ -1,4 +1,4 @@
|
||||||
# Rdio Scanner v3.0
|
# Rdio Scanner v3.1
|
||||||
|
|
||||||
*Rdio Scanner* is a progressive web interface designed to act like an old school police radio scanner. It integrates all frontend / backend components to manage audio files from different sources.
|
*Rdio Scanner* is a progressive web interface designed to act like an old school police radio scanner. It integrates all frontend / backend components to manage audio files from different sources.
|
||||||
|
|
||||||
|
|
@ -10,11 +10,10 @@ Need help?
|
||||||
|
|
||||||
## What's new in this version
|
## What's new in this version
|
||||||
|
|
||||||
Version 3.0 brings new features:
|
Version 3.1 brings new features:
|
||||||
|
|
||||||
* Unit aliases support, display names instead of unit ID
|
* Code refactoring
|
||||||
* Download calls from the search pane
|
* Display live feed listeners count on server end
|
||||||
* New configuration options: *allowDownload* and *useGroup*
|
|
||||||
|
|
||||||
*Note that you can only update from version 2.0 and above. You have to do a fresh install if your actual version is prior to version 2.0.*
|
*Note that you can only update from version 2.0 and above. You have to do a fresh install if your actual version is prior to version 2.0.*
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "rdio-scanner-client",
|
"name": "rdio-scanner-client",
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "ng build --cross-origin use-credentials --prod $*",
|
"build": "ng build --cross-origin use-credentials --prod $*",
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ export interface RdioScannerCallsQueryResponse {
|
||||||
export class AppRdioScannerCallsQueryService extends Query<RdioScannerCallsQueryResponse> {
|
export class AppRdioScannerCallsQueryService extends Query<RdioScannerCallsQueryResponse> {
|
||||||
document = gql`
|
document = gql`
|
||||||
query rdioScannerCalls(
|
query rdioScannerCalls(
|
||||||
$date: Date
|
$date: RdioScannerDate
|
||||||
$limit: Int
|
$limit: Int
|
||||||
$offset: Int
|
$offset: Int
|
||||||
$sort: Int
|
$sort: Int
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@
|
||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
<ng-container *ngIf="config.useGroup">
|
<ng-container *ngIf="config.useGroup && groups.length">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div fxLayout="row wrap" fxLayoutAlign="space-between">
|
<div fxLayout="row wrap" fxLayoutAlign="space-between">
|
||||||
<button *ngFor="let group of groups" class="rdio-button" (click)="toggleGroup(group.name)"
|
<button *ngFor="let group of groups" class="rdio-button" (click)="toggleGroup(group.name)"
|
||||||
|
|
|
||||||
|
|
@ -344,7 +344,7 @@ export class AppRdioScannerComponent implements OnDestroy, OnInit {
|
||||||
this.subscribeSearchForm();
|
this.subscribeSearchForm();
|
||||||
this.subscribeSearchPaginator();
|
this.subscribeSearchPaginator();
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -567,10 +567,6 @@ export class AppRdioScannerComponent implements OnDestroy, OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildGroups(): void {
|
private buildGroups(): void {
|
||||||
if (!this.config.useGroup) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const groups: RdioScannerGroup[] = [];
|
const groups: RdioScannerGroup[] = [];
|
||||||
|
|
||||||
this.systems.forEach((system: RdioScannerSystem) => {
|
this.systems.forEach((system: RdioScannerSystem) => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "rdio-scanner",
|
"name": "rdio-scanner",
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "run.js",
|
"main": "run.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,21 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
require('dotenv').config();
|
const config = {
|
||||||
|
database: process.env.DB_NAME,
|
||||||
const defaultConfig = {
|
dialect: process.env.DB_DIALECT || 'sqlite',
|
||||||
database: '',
|
|
||||||
dialect: 'sqlite',
|
|
||||||
dialectOptions: {
|
dialectOptions: {
|
||||||
timezone: process.env.DB_TZ || 'Etc/GMT0',
|
timezone: process.env.DB_TZ || 'Etc/GMT0',
|
||||||
},
|
},
|
||||||
host: '',
|
host: process.env.DB_HOST,
|
||||||
logging: false,
|
logging: false,
|
||||||
password: '',
|
password: process.env.DB_PASS,
|
||||||
port: '',
|
port: process.env.DB_PORT,
|
||||||
storage: 'database.sqlite',
|
storage: process.env.DB_STORAGE || 'database.sqlite',
|
||||||
username: '',
|
username: process.env.DB_USER,
|
||||||
};
|
};
|
||||||
|
|
||||||
function getConfig() {
|
|
||||||
return {
|
|
||||||
database: process.env.DB_NAME || defaultConfig.database,
|
|
||||||
dialect: process.env.DB_DIALECT || defaultConfig.dialect,
|
|
||||||
dialectOptions: defaultConfig.dialectOptions,
|
|
||||||
host: process.env.DB_HOST || defaultConfig.host,
|
|
||||||
logging: defaultConfig.logging,
|
|
||||||
password: process.env.DB_PASS || defaultConfig.password,
|
|
||||||
port: process.env.DB_PORT || defaultConfig.port,
|
|
||||||
storage: process.env.DB_STORAGE || defaultConfig.storage,
|
|
||||||
username: process.env.DB_USER || defaultConfig.username,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
development: getConfig(),
|
development: config,
|
||||||
production: getConfig(),
|
production: config,
|
||||||
test: getConfig(),
|
test: config,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
5
server/config/index.js
Normal file
5
server/config/index.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports = ({ store }) => {
|
|
||||||
const camelCase = require('camelcase');
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const basename = path.basename(__filename);
|
|
||||||
const dataSources = {};
|
|
||||||
|
|
||||||
fs.readdirSync(__dirname)
|
|
||||||
.filter((file) => (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'))
|
|
||||||
.map((file) => file.slice(0, -3))
|
|
||||||
.forEach((file) => {
|
|
||||||
const ds = require(`./${file}`);
|
|
||||||
const name = camelCase(file);
|
|
||||||
dataSources[name] = new ds({ store });
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => dataSources;
|
|
||||||
};
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const { DataSource } = require('apollo-datasource');
|
|
||||||
const { callReducer } = require('../../helpers/rdio-scanner');
|
|
||||||
|
|
||||||
class RdioScannerSystem extends DataSource {
|
|
||||||
constructor({ store }) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.store = store;
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(config) {
|
|
||||||
this.context = config.context;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCall(id) {
|
|
||||||
const result = await this.store.rdioScannerCall.findByPk(id);
|
|
||||||
|
|
||||||
return callReducer(result.dataValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCalls({ date, system, talkgroup }, { limit, offset, sort }) {
|
|
||||||
const Op = this.store.Sequelize.Op;
|
|
||||||
|
|
||||||
const attributes = {
|
|
||||||
exclude: ['audio', 'freqList', 'srcList'],
|
|
||||||
};
|
|
||||||
|
|
||||||
const order = [['startTime', typeof sort === 'number' && sort < 0 ? 'DESC' : 'ASC']];
|
|
||||||
|
|
||||||
const where = {};
|
|
||||||
|
|
||||||
if (typeof system === 'number') {
|
|
||||||
where.system = system;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof talkgroup === 'number') {
|
|
||||||
where.talkgroup = talkgroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateStartQuery = await this.store.rdioScannerCall.findOne({ order: [['startTime', 'ASC']], where });
|
|
||||||
|
|
||||||
const dateStart = dateStartQuery && dateStartQuery.get('startTime');
|
|
||||||
|
|
||||||
const dateStopQuery = await this.store.rdioScannerCall.findOne({ order: [['startTime', 'DESC']], where });
|
|
||||||
|
|
||||||
const dateStop = dateStopQuery && dateStopQuery.get('startTime');
|
|
||||||
|
|
||||||
if (typeof date === 'string') {
|
|
||||||
const d = new Date(date);
|
|
||||||
|
|
||||||
where.startTime = {
|
|
||||||
[Op.gte]: new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0),
|
|
||||||
[Op.lte]: new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const count = await this.store.rdioScannerCall.count({ where });
|
|
||||||
|
|
||||||
limit = typeof limit === 'number' ? limit : 100;
|
|
||||||
offset = typeof offset === 'number' ? offset : 0;
|
|
||||||
|
|
||||||
const calls = await this.store.rdioScannerCall.findAll({ attributes, limit, offset, order, where });
|
|
||||||
|
|
||||||
const results = callReducer(calls);
|
|
||||||
|
|
||||||
return { count, dateStart, dateStop, results };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = RdioScannerSystem;
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const { DataSource } = require('apollo-datasource');
|
|
||||||
const { callReducer } = require('../../helpers/rdio-scanner');
|
|
||||||
|
|
||||||
class RdioScannerSystem extends DataSource {
|
|
||||||
constructor({ store }) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.store = store;
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(config) {
|
|
||||||
this.context = config.context;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCall(id) {
|
|
||||||
const result = await this.store.rdioScannerCall.findByPk(id);
|
|
||||||
|
|
||||||
return callReducer(result.dataValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCalls({ date, system, talkgroup }, { limit, offset, sort }) {
|
|
||||||
const Op = this.store.Sequelize.Op;
|
|
||||||
|
|
||||||
const attributes = {
|
|
||||||
exclude: ['audio', 'freqList', 'srcList'],
|
|
||||||
};
|
|
||||||
|
|
||||||
const order = [['startTime', typeof sort === 'number' && sort < 0 ? 'DESC' : 'ASC']];
|
|
||||||
|
|
||||||
const where = {};
|
|
||||||
|
|
||||||
if (typeof system === 'number') {
|
|
||||||
where.system = system;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof talkgroup === 'number') {
|
|
||||||
where.talkgroup = talkgroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateStartQuery = await this.store.rdioScannerCall.findOne({ order: [['startTime', 'ASC']], where });
|
|
||||||
|
|
||||||
const dateStart = dateStartQuery && dateStartQuery.get('startTime');
|
|
||||||
|
|
||||||
const dateStopQuery = await this.store.rdioScannerCall.findOne({ order: [['startTime', 'DESC']], where });
|
|
||||||
|
|
||||||
const dateStop = dateStopQuery && dateStopQuery.get('startTime');
|
|
||||||
|
|
||||||
if (typeof date === 'string') {
|
|
||||||
const d = new Date(date);
|
|
||||||
|
|
||||||
where.startTime = {
|
|
||||||
[Op.gte]: new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0),
|
|
||||||
[Op.lte]: new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const count = await this.store.rdioScannerCall.count({ where });
|
|
||||||
|
|
||||||
limit = typeof limit === 'number' ? limit : 100;
|
|
||||||
offset = typeof offset === 'number' ? offset : 0;
|
|
||||||
|
|
||||||
const calls = await this.store.rdioScannerCall.findAll({ attributes, limit, offset, order, where });
|
|
||||||
|
|
||||||
const results = callReducer(calls);
|
|
||||||
|
|
||||||
return { count, dateStart, dateStop, results };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = RdioScannerSystem;
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const { DataSource } = require('apollo-datasource');
|
|
||||||
const { systemReducer } = require ('../../helpers/rdio-scanner');
|
|
||||||
|
|
||||||
class RdioScannerSystem extends DataSource {
|
|
||||||
constructor({ store }) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.store = store;
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(config) {
|
|
||||||
this.context = config.context;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSystems() {
|
|
||||||
const systems = await this.store.rdioScannerSystem.findAll({
|
|
||||||
order: [['system', 'ASC']],
|
|
||||||
});
|
|
||||||
return systemReducer(systems);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = RdioScannerSystem;
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const camelCase = require('camelcase');
|
|
||||||
const fs = require('fs');
|
|
||||||
const { gql } = require('apollo-server-express');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
function graphql({ pubsub, store }) {
|
|
||||||
|
|
||||||
const dataSources = require('./datasources')({ store });
|
|
||||||
|
|
||||||
const query = readDir('queries', pubsub);
|
|
||||||
const scalar = readDir('scalars', pubsub);
|
|
||||||
const subscription = readDir('subscriptions', pubsub);
|
|
||||||
const type = readDir('types', pubsub);
|
|
||||||
|
|
||||||
const typeDefs = gql([
|
|
||||||
scalar.schemas,
|
|
||||||
'type Query {', query.schemas, '}', '',
|
|
||||||
'type Subscription {', subscription.schemas, '}', '',
|
|
||||||
type.schemas
|
|
||||||
].join('\n'));
|
|
||||||
|
|
||||||
const resolvers = {
|
|
||||||
Query: query.resolvers,
|
|
||||||
Subscription: subscription.resolvers,
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
dataSources,
|
|
||||||
resolvers,
|
|
||||||
typeDefs,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function readDir(dir, pubsub) {
|
|
||||||
let schemas = '';
|
|
||||||
const resolvers = {};
|
|
||||||
|
|
||||||
dir = path.resolve(__dirname, dir);
|
|
||||||
|
|
||||||
const filenames = fs.readdirSync(dir);
|
|
||||||
|
|
||||||
filenames.filter((file) => (file.slice(-4) === '.gql'))
|
|
||||||
.forEach((file) => {
|
|
||||||
const schema = fs.readFileSync(path.join(dir, file));
|
|
||||||
|
|
||||||
schemas += `${schema}\n`;
|
|
||||||
})
|
|
||||||
|
|
||||||
filenames.filter((file) => (file.indexOf('.') !== 0) && (file.slice(-3) === '.js'))
|
|
||||||
.map((file) => file.slice(0, -3))
|
|
||||||
.forEach((file) => {
|
|
||||||
const resolver = require(path.join(dir, file))({ pubsub });
|
|
||||||
const name = camelCase(file);
|
|
||||||
|
|
||||||
resolvers[name] = resolver;
|
|
||||||
});
|
|
||||||
|
|
||||||
return { resolvers, schemas };
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = graphql;
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
rdioScannerCall(id: String): RdioScannerCall
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports = () => async (_, { id }, { dataSources }) => {
|
|
||||||
const rdioScannerCall = await dataSources.rdioScannerCall.getCall(id);
|
|
||||||
|
|
||||||
return rdioScannerCall;
|
|
||||||
};
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
rdioScannerCalls(
|
|
||||||
date: Date
|
|
||||||
limit: Int
|
|
||||||
offset: Int
|
|
||||||
sort: Int
|
|
||||||
system: Int
|
|
||||||
talkgroup: Int
|
|
||||||
): RdioScannerCallsQueryResponse
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports = () => async (_, { date, limit, offset, sort, system, talkgroup }, { dataSources }) => {
|
|
||||||
const { count, dateStart, dateStop, results } = await dataSources.rdioScannerCall.getCalls({ date, system, talkgroup }, { limit, offset, sort });
|
|
||||||
|
|
||||||
return { count, dateStart, dateStop, results };
|
|
||||||
};
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
rdioScannerConfig: RdioScannerConfig
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
require('dotenv').config();
|
|
||||||
|
|
||||||
module.exports = () => async () => {
|
|
||||||
let allowDownload = process.env.RDIO_ALLOW_DOWNLOAD || true;
|
|
||||||
|
|
||||||
allowDownload = `${allowDownload}`.toLowerCase() !== 'false';
|
|
||||||
|
|
||||||
let useGroup = process.env.RDIO_USE_GROUP || true;
|
|
||||||
|
|
||||||
useGroup = `${useGroup}`.toLowerCase() !== 'false';
|
|
||||||
|
|
||||||
return { allowDownload, useGroup };
|
|
||||||
};
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
rdioScannerSystems: [RdioScannerSystem]
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports = () => async (_, __, { dataSources }) => {
|
|
||||||
return await dataSources.rdioScannerSystem.getSystems();
|
|
||||||
};
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
scalar Audio
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
scalar Date
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
rdioScannerCall(selection: String): RdioScannerCall
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const { withFilter } = require('apollo-server-express');
|
|
||||||
|
|
||||||
module.exports = ({ pubsub }) => ({
|
|
||||||
subscribe: withFilter(
|
|
||||||
() => pubsub.asyncIterator(['rdioScannerCall']),
|
|
||||||
(payload, variables) => {
|
|
||||||
if (variables.selection) {
|
|
||||||
if (payload.rdioScannerCall) {
|
|
||||||
const call = payload.rdioScannerCall;
|
|
||||||
|
|
||||||
let selection;
|
|
||||||
|
|
||||||
try {
|
|
||||||
selection = JSON.parse(variables.selection);
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return selection[call.system][call.talkgroup];
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
rdioScannerSystems: [RdioScannerSystem]
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports = ({ pubsub }) => ({
|
|
||||||
subscribe: () => pubsub.asyncIterator(['rdioScannerSystems']),
|
|
||||||
});
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
type RdioScannerAlias {
|
|
||||||
name: String
|
|
||||||
uid: Int
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
type RdioScannerCallFreq {
|
|
||||||
errorCount: Int
|
|
||||||
freq: Int
|
|
||||||
len: Int
|
|
||||||
pos: Float
|
|
||||||
spikeCount: Int
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
type RdioScannerCallSrc {
|
|
||||||
pos: Float
|
|
||||||
src: Int
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
type RdioScannerCall {
|
|
||||||
id: String
|
|
||||||
audio: Audio
|
|
||||||
audioName: String
|
|
||||||
audioType: String
|
|
||||||
emergency: Boolean
|
|
||||||
freq: Int
|
|
||||||
freqList: [RdioScannerCallFreq]
|
|
||||||
startTime: Date
|
|
||||||
stopTime: Date
|
|
||||||
srcList: [RdioScannerCallSrc]
|
|
||||||
system: Int
|
|
||||||
talkgroup: Int
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
type RdioScannerCallsQueryResponse {
|
|
||||||
count: Int
|
|
||||||
dateStart: Date
|
|
||||||
dateStop: Date
|
|
||||||
results: [RdioScannerCall]
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
type RdioScannerConfig {
|
|
||||||
allowDownload: Boolean
|
|
||||||
useGroup: Boolean
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
type RdioScannerSystem {
|
|
||||||
aliases: [RdioScannerAlias]
|
|
||||||
id: String
|
|
||||||
name: String
|
|
||||||
system: Int
|
|
||||||
talkgroups: [RdioScannerTalkgroup]
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
type RdioScannerTalkgroup {
|
|
||||||
alphaTag: String
|
|
||||||
dec: Int
|
|
||||||
description: String
|
|
||||||
group: String
|
|
||||||
mode: String
|
|
||||||
tag: String
|
|
||||||
}
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function callReducer(call) {
|
|
||||||
if (Array.isArray(call)) {
|
|
||||||
return call.map((c) => callReducer(c));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
call.freqList = JSON.parse(call.freqList);
|
|
||||||
} catch (err) {
|
|
||||||
call.freqList = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
call.srcList = JSON.parse(call.srcList);
|
|
||||||
} catch (err) {
|
|
||||||
call.srcList = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return call;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getApiKeys() {
|
|
||||||
let apiKeys = ['b29eb8b9-9bcd-4e6e-bb4f-d244ada12736'];
|
|
||||||
|
|
||||||
if (process.env.RDIO_APIKEYS) {
|
|
||||||
try {
|
|
||||||
apiKeys = JSON.parse(process.env.RDIO_APIKEYS);
|
|
||||||
} catch (err) {
|
|
||||||
console.log('ERROR: Invalid JSON format in RDIO_APIKEYS.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return apiKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
function pruningScheduler({ store }) {
|
|
||||||
let pruneDays = parseInt(process.env.RDIO_PRUNEDAYS, 10);
|
|
||||||
|
|
||||||
pruneDays = isNaN(pruneDays) ? 7 : pruneDays;
|
|
||||||
|
|
||||||
if (store === null || typeof store !== 'object' || pruneDays < 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Op = store.Sequelize && store.Sequelize.Op || {};
|
|
||||||
|
|
||||||
return setInterval(() => {
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
store.rdioScannerCall.destroy({
|
|
||||||
where: {
|
|
||||||
startTime: {
|
|
||||||
[Op.lt]: new Date(now.getFullYear(), now.getMonth(), now.getDate() - pruneDays),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, 15 * 60 * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function systemReducer(system) {
|
|
||||||
if (Array.isArray(system)) {
|
|
||||||
return system.map((s) => systemReducer(s));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
system.aliases = JSON.parse(system.aliases);
|
|
||||||
} catch (err) {
|
|
||||||
system.aliases = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
system.talkgroups = JSON.parse(system.talkgroups);
|
|
||||||
} catch (err) {
|
|
||||||
system.talkgroups = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return system;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateApiKey(key, system, talkgroup) {
|
|
||||||
const apiKeys = getApiKeys();
|
|
||||||
|
|
||||||
if (typeof apiKeys === 'string') {
|
|
||||||
return apiKeys === key;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(apiKeys)) {
|
|
||||||
return apiKeys.some((apiKey) => {
|
|
||||||
if (typeof apiKey === 'string') {
|
|
||||||
return apiKey === key;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (apiKey !== null && typeof apiKey === 'object') {
|
|
||||||
return apiKey.key === key && Array.isArray(apiKey.systems) && apiKey.systems.some((sys) => {
|
|
||||||
if (typeof sys === 'number') {
|
|
||||||
return sys === system;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sys !== null && typeof sys === 'object') {
|
|
||||||
if (typeof talkgroup === 'undefined') {
|
|
||||||
return sys.system === +system;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sys.system === +system && Array.isArray(sys.talkgroups) && sys.talkgroups.includes(+talkgroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { callReducer, pruningScheduler, systemReducer, validateApiKey };
|
|
||||||
|
|
@ -2,72 +2,49 @@
|
||||||
|
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
const { ApolloServer, PubSub } = require('apollo-server-express');
|
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
const fs = require('fs');
|
||||||
const helmet = require('helmet');
|
const helmet = require('helmet');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const RdioScanner = require('./lib/rdio-scanner');
|
||||||
|
|
||||||
const env = process.env.NODE_ENV || 'development';
|
const env = process.env.NODE_ENV || 'development';
|
||||||
const host = process.env.NODE_HOST || '0.0.0.0';
|
const host = process.env.NODE_HOST || '0.0.0.0';
|
||||||
|
const staticDir = path.resolve(process.env.APP_CLIENT_DIR || '../client/dist');
|
||||||
|
const staticFile = process.env.APP_CLIENT_FILE || 'main.html';
|
||||||
const port = parseInt(process.env.NODE_PORT, 10) || 3000;
|
const port = parseInt(process.env.NODE_PORT, 10) || 3000;
|
||||||
|
|
||||||
const store = require('./models');
|
class App {
|
||||||
|
constructor() {
|
||||||
|
this.router = express();
|
||||||
|
this.router.use(express.json());
|
||||||
|
this.router.use(express.urlencoded({ extended: false }));
|
||||||
|
this.router.use(express.static(staticDir));
|
||||||
|
this.router.set(port);
|
||||||
|
|
||||||
const pubsub = new PubSub();
|
if (env !== 'development') {
|
||||||
|
this.router.disable('x-powered-by');
|
||||||
|
|
||||||
const { dataSources, resolvers, typeDefs } = require('./graphql')({ pubsub, store });
|
this.router.get(/\/|\/index.html/, cors(), (req, res) => {
|
||||||
|
if (fs.existsSync(path.join(staticDir, staticFile))) {
|
||||||
|
return res.sendFile(staticFile, { root: staticDir });
|
||||||
|
|
||||||
const context = async ({ connection, req }) => {
|
} else {
|
||||||
if (connection) {
|
return res.send('A new build is being prepared. Please check back in a few minutes.');
|
||||||
return connection.context;
|
}
|
||||||
|
});
|
||||||
|
|
||||||
} else {
|
this.router.use(helmet());
|
||||||
return {
|
}
|
||||||
token: req.headers.authorization || '',
|
|
||||||
};
|
this.httpServer = http.createServer(this.router);
|
||||||
|
this.httpServer.listen(port, host, () => console.log(`Server is running at http://${host}:${port}/`));
|
||||||
|
|
||||||
|
this.rdioScanner = new RdioScanner(this.httpServer, this.router);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = express();
|
module.exports = new App();
|
||||||
|
|
||||||
const httpServer = http.createServer(app);
|
|
||||||
|
|
||||||
const apolloServer = new ApolloServer({
|
|
||||||
context,
|
|
||||||
dataSources,
|
|
||||||
resolvers,
|
|
||||||
typeDefs,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { pruningScheduler } = require('./helpers/rdio-scanner');
|
|
||||||
|
|
||||||
const clientRoot = path.join(__dirname, '../client/dist');
|
|
||||||
|
|
||||||
pruningScheduler({ store });
|
|
||||||
|
|
||||||
app.use(express.json());
|
|
||||||
app.use(express.urlencoded({ extended: false }));
|
|
||||||
app.use(express.static(clientRoot));
|
|
||||||
|
|
||||||
app.post('/api/trunk-recorder-alias-upload', require('./routes/trunk-recorder-alias-upload')({ pubsub, store }));
|
|
||||||
app.post('/api/trunk-recorder-call-upload', require('./routes/trunk-recorder-call-upload')({ pubsub, store }));
|
|
||||||
app.post('/api/trunk-recorder-system-upload', require('./routes/trunk-recorder-system-upload')({ pubsub, store }));
|
|
||||||
|
|
||||||
app.set('port', port);
|
|
||||||
|
|
||||||
if (env !== 'development') {
|
|
||||||
app.disable('x-powered-by');
|
|
||||||
|
|
||||||
app.get(/\/|\/index.html/, cors(), require('./routes/main')({ clientRoot }));
|
|
||||||
|
|
||||||
app.use(helmet());
|
|
||||||
}
|
|
||||||
|
|
||||||
apolloServer.applyMiddleware({ app, cors: true });
|
|
||||||
apolloServer.installSubscriptionHandlers(httpServer);
|
|
||||||
|
|
||||||
httpServer.listen(port, host, () => console.log(`Rdio Scanner is running at http://${host}:${port}/`));
|
|
||||||
|
|
||||||
module.exports = app;
|
|
||||||
|
|
|
||||||
11
server/lib/rdio-scanner/graphql/datasources/get-call.js
Normal file
11
server/lib/rdio-scanner/graphql/datasources/get-call.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function getCall(models) {
|
||||||
|
return async (id) => {
|
||||||
|
const result = await models.rdioScannerCall.findByPk(id);
|
||||||
|
|
||||||
|
return result.dataValues;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getCall;
|
||||||
51
server/lib/rdio-scanner/graphql/datasources/get-calls.js
Normal file
51
server/lib/rdio-scanner/graphql/datasources/get-calls.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { Op } = require('sequelize');
|
||||||
|
|
||||||
|
function getCalls(models) {
|
||||||
|
return async ({ date, system, talkgroup }, { limit, offset, sort }) => {
|
||||||
|
const attributes = {
|
||||||
|
exclude: ['audio', 'freqList', 'srcList'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const order = [['startTime', typeof sort === 'number' && sort < 0 ? 'DESC' : 'ASC']];
|
||||||
|
|
||||||
|
const where = {};
|
||||||
|
|
||||||
|
if (typeof system === 'number') {
|
||||||
|
where.system = system;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof talkgroup === 'number') {
|
||||||
|
where.talkgroup = talkgroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateStartQuery = await models.rdioScannerCall.findOne({ order: [['startTime', 'ASC']], where });
|
||||||
|
|
||||||
|
const dateStart = dateStartQuery && dateStartQuery.get('startTime');
|
||||||
|
|
||||||
|
const dateStopQuery = await models.rdioScannerCall.findOne({ order: [['startTime', 'DESC']], where });
|
||||||
|
|
||||||
|
const dateStop = dateStopQuery && dateStopQuery.get('startTime');
|
||||||
|
|
||||||
|
if (typeof date === 'string') {
|
||||||
|
const d = new Date(date);
|
||||||
|
|
||||||
|
where.startTime = {
|
||||||
|
[Op.gte]: new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0),
|
||||||
|
[Op.lte]: new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = await models.rdioScannerCall.count({ where });
|
||||||
|
|
||||||
|
limit = typeof limit === 'number' ? limit : 100;
|
||||||
|
offset = typeof offset === 'number' ? offset : 0;
|
||||||
|
|
||||||
|
const results = await models.rdioScannerCall.findAll({ attributes, limit, offset, order, where });
|
||||||
|
|
||||||
|
return { count, dateStart, dateStop, results };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getCalls;
|
||||||
11
server/lib/rdio-scanner/graphql/datasources/get-systems.js
Normal file
11
server/lib/rdio-scanner/graphql/datasources/get-systems.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function getSystems(models) {
|
||||||
|
return async () => {
|
||||||
|
const systems = await models.rdioScannerSystem.findAll();
|
||||||
|
|
||||||
|
return systems;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getSystems;
|
||||||
25
server/lib/rdio-scanner/graphql/datasources/index.js
Normal file
25
server/lib/rdio-scanner/graphql/datasources/index.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { DataSource } = require('apollo-datasource');
|
||||||
|
|
||||||
|
const getCall = require('./get-call');
|
||||||
|
const getCalls = require('./get-calls');
|
||||||
|
const getSystems = require('./get-systems');
|
||||||
|
|
||||||
|
class DataSources extends DataSource {
|
||||||
|
constructor(models) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.rdioScanner = {
|
||||||
|
getCall: getCall(models),
|
||||||
|
getCalls: getCalls(models),
|
||||||
|
getSystems: getSystems(models),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize(config) {
|
||||||
|
this.context = config.context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DataSources;
|
||||||
66
server/lib/rdio-scanner/graphql/index.js
Normal file
66
server/lib/rdio-scanner/graphql/index.js
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { ApolloServer, gql } = require('apollo-server-express');
|
||||||
|
|
||||||
|
const DataSources = require('./datasources');
|
||||||
|
const Queries = require('./queries');
|
||||||
|
const Scalars = require('./scalars');
|
||||||
|
const Subscriptions = require('./subscriptions');
|
||||||
|
const Types = require('./types');
|
||||||
|
|
||||||
|
class GraphQL {
|
||||||
|
static get DataSources() {
|
||||||
|
return DataSources;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get Queries() {
|
||||||
|
return Queries;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get Scalars() {
|
||||||
|
return Scalars;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get Subscriptions() {
|
||||||
|
return Subscriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get Types() {
|
||||||
|
return Types;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(models, pubsub) {
|
||||||
|
const dataSources = () => new DataSources(models);
|
||||||
|
|
||||||
|
const queries = new Queries(models);
|
||||||
|
|
||||||
|
const scalars = new Scalars(models);
|
||||||
|
|
||||||
|
const subscriptions = new Subscriptions(pubsub);
|
||||||
|
|
||||||
|
const types = new Types(models);
|
||||||
|
|
||||||
|
const typeDefs = gql`
|
||||||
|
${scalars.schemas}
|
||||||
|
|
||||||
|
${types.schemas}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
${queries.schemas}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subscription {
|
||||||
|
${subscriptions.schemas}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const resolvers = Object.assign({},
|
||||||
|
{ Query: queries.resolvers },
|
||||||
|
{ Subscription: subscriptions.resolvers },
|
||||||
|
);
|
||||||
|
|
||||||
|
return new ApolloServer({ dataSources, resolvers, typeDefs });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = GraphQL;
|
||||||
17
server/lib/rdio-scanner/graphql/queries/call.js
Normal file
17
server/lib/rdio-scanner/graphql/queries/call.js
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
rdioScannerCall(id: String): RdioScannerCall
|
||||||
|
`;
|
||||||
|
|
||||||
|
function resolver() {
|
||||||
|
return {
|
||||||
|
async rdioScannerCall(_, { id }, { dataSources }) {
|
||||||
|
const rdioScannerCall = await dataSources.rdioScanner.getCall(id);
|
||||||
|
|
||||||
|
return rdioScannerCall;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { resolver, schema };
|
||||||
24
server/lib/rdio-scanner/graphql/queries/calls.js
Normal file
24
server/lib/rdio-scanner/graphql/queries/calls.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
rdioScannerCalls(
|
||||||
|
date: RdioScannerDate
|
||||||
|
limit: Int
|
||||||
|
offset: Int
|
||||||
|
sort: Int
|
||||||
|
system: Int
|
||||||
|
talkgroup: Int
|
||||||
|
): RdioScannerCallsQueryResponse
|
||||||
|
`;
|
||||||
|
|
||||||
|
function resolver() {
|
||||||
|
return {
|
||||||
|
async rdioScannerCalls(_, { date, limit, offset, sort, system, talkgroup }, { dataSources }) {
|
||||||
|
const { count, dateStart, dateStop, results } = await dataSources.rdioScanner.getCalls({ date, system, talkgroup }, { limit, offset, sort });
|
||||||
|
|
||||||
|
return { count, dateStart, dateStop, results };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { resolver, schema };
|
||||||
23
server/lib/rdio-scanner/graphql/queries/config.js
Normal file
23
server/lib/rdio-scanner/graphql/queries/config.js
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
rdioScannerConfig: RdioScannerConfig
|
||||||
|
`;
|
||||||
|
|
||||||
|
function resolver() {
|
||||||
|
return {
|
||||||
|
async rdioScannerConfig() {
|
||||||
|
let allowDownload = process.env.RDIO_ALLOW_DOWNLOAD || true;
|
||||||
|
|
||||||
|
allowDownload = `${allowDownload}`.toLowerCase() !== 'false';
|
||||||
|
|
||||||
|
let useGroup = process.env.RDIO_USE_GROUP || true;
|
||||||
|
|
||||||
|
useGroup = `${useGroup}`.toLowerCase() !== 'false';
|
||||||
|
|
||||||
|
return { allowDownload, useGroup };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { resolver, schema };
|
||||||
26
server/lib/rdio-scanner/graphql/queries/index.js
Normal file
26
server/lib/rdio-scanner/graphql/queries/index.js
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const call = require('./call');
|
||||||
|
const calls = require('./calls');
|
||||||
|
const config = require('./config');
|
||||||
|
const systems = require('./systems');
|
||||||
|
|
||||||
|
class Queries {
|
||||||
|
constructor(models) {
|
||||||
|
this.schemas = `
|
||||||
|
${call.schema}
|
||||||
|
${calls.schema}
|
||||||
|
${config.schema}
|
||||||
|
${systems.schema}
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.resolvers = Object.assign({},
|
||||||
|
call.resolver(models),
|
||||||
|
calls.resolver(models),
|
||||||
|
config.resolver(models),
|
||||||
|
systems.resolver(models),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Queries;
|
||||||
15
server/lib/rdio-scanner/graphql/queries/systems.js
Normal file
15
server/lib/rdio-scanner/graphql/queries/systems.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
rdioScannerSystems: [RdioScannerSystem]
|
||||||
|
`;
|
||||||
|
|
||||||
|
function resolver() {
|
||||||
|
return {
|
||||||
|
async rdioScannerSystems(_, __, { dataSources }) {
|
||||||
|
return await dataSources.rdioScanner.getSystems();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { resolver, schema };
|
||||||
7
server/lib/rdio-scanner/graphql/scalars/audio.js
Normal file
7
server/lib/rdio-scanner/graphql/scalars/audio.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
scalar RdioScannerAudio
|
||||||
|
`;
|
||||||
|
|
||||||
|
module.exports = { schema };
|
||||||
7
server/lib/rdio-scanner/graphql/scalars/date.js
Normal file
7
server/lib/rdio-scanner/graphql/scalars/date.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
scalar RdioScannerDate
|
||||||
|
`;
|
||||||
|
|
||||||
|
module.exports = { schema };
|
||||||
15
server/lib/rdio-scanner/graphql/scalars/index.js
Normal file
15
server/lib/rdio-scanner/graphql/scalars/index.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const audio = require('./audio');
|
||||||
|
const date = require('./date');
|
||||||
|
|
||||||
|
class Scalars {
|
||||||
|
constructor() {
|
||||||
|
this.schemas = `
|
||||||
|
${audio.schema}
|
||||||
|
${date.schema}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Scalars;
|
||||||
43
server/lib/rdio-scanner/graphql/subscriptions/call.js
Normal file
43
server/lib/rdio-scanner/graphql/subscriptions/call.js
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { withFilter } = require('apollo-server-express');
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
rdioScannerCall(selection: String): RdioScannerCall
|
||||||
|
`;
|
||||||
|
|
||||||
|
function resolver(pubsub) {
|
||||||
|
return {
|
||||||
|
rdioScannerCall: {
|
||||||
|
subscribe: withFilter(
|
||||||
|
() => pubsub.asyncIterator(['rdioScannerCall']),
|
||||||
|
(payload, variables) => {
|
||||||
|
if (variables.selection) {
|
||||||
|
if (payload.rdioScannerCall) {
|
||||||
|
const call = payload.rdioScannerCall;
|
||||||
|
|
||||||
|
let selection;
|
||||||
|
|
||||||
|
try {
|
||||||
|
selection = JSON.parse(variables.selection);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selection[call.system][call.talkgroup];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { resolver, schema };
|
||||||
20
server/lib/rdio-scanner/graphql/subscriptions/index.js
Normal file
20
server/lib/rdio-scanner/graphql/subscriptions/index.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const call = require('./call');
|
||||||
|
const systems = require('./systems');
|
||||||
|
|
||||||
|
class Subscriptions {
|
||||||
|
constructor(pubsub) {
|
||||||
|
this.schemas = `
|
||||||
|
${call.schema}
|
||||||
|
${systems.schema}
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.resolvers = Object.assign({},
|
||||||
|
call.resolver(pubsub),
|
||||||
|
systems.resolver(pubsub),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Subscriptions;
|
||||||
15
server/lib/rdio-scanner/graphql/subscriptions/systems.js
Normal file
15
server/lib/rdio-scanner/graphql/subscriptions/systems.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
rdioScannerSystems: [RdioScannerSystem]
|
||||||
|
`;
|
||||||
|
|
||||||
|
function resolver(pubsub) {
|
||||||
|
return {
|
||||||
|
rdioScannerSystems: {
|
||||||
|
subscribe: () => pubsub.asyncIterator(['rdioScannerSystems']),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { resolver, schema };
|
||||||
10
server/lib/rdio-scanner/graphql/types/alias.js
Normal file
10
server/lib/rdio-scanner/graphql/types/alias.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
type RdioScannerAlias {
|
||||||
|
name: String
|
||||||
|
uid: Int
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
module.exports = { schema };
|
||||||
13
server/lib/rdio-scanner/graphql/types/call-freq.js
Normal file
13
server/lib/rdio-scanner/graphql/types/call-freq.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
type RdioScannerCallFreq {
|
||||||
|
errorCount: Int
|
||||||
|
freq: Int
|
||||||
|
len: Int
|
||||||
|
pos: Float
|
||||||
|
spikeCount: Int
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
module.exports = { schema };
|
||||||
12
server/lib/rdio-scanner/graphql/types/call-query-response.js
Normal file
12
server/lib/rdio-scanner/graphql/types/call-query-response.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
type RdioScannerCallsQueryResponse {
|
||||||
|
count: Int
|
||||||
|
dateStart: RdioScannerDate
|
||||||
|
dateStop: RdioScannerDate
|
||||||
|
results: [RdioScannerCall]
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
module.exports = { schema };
|
||||||
10
server/lib/rdio-scanner/graphql/types/call-src.js
Normal file
10
server/lib/rdio-scanner/graphql/types/call-src.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
type RdioScannerCallSrc {
|
||||||
|
pos: Float
|
||||||
|
src: Int
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
module.exports = { schema };
|
||||||
20
server/lib/rdio-scanner/graphql/types/call.js
Normal file
20
server/lib/rdio-scanner/graphql/types/call.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
type RdioScannerCall {
|
||||||
|
id: String
|
||||||
|
audio: RdioScannerAudio
|
||||||
|
audioName: String
|
||||||
|
audioType: String
|
||||||
|
emergency: Boolean
|
||||||
|
freq: Int
|
||||||
|
freqList: [RdioScannerCallFreq]
|
||||||
|
startTime: RdioScannerDate
|
||||||
|
stopTime: RdioScannerDate
|
||||||
|
srcList: [RdioScannerCallSrc]
|
||||||
|
system: Int
|
||||||
|
talkgroup: Int
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
module.exports = { schema };
|
||||||
10
server/lib/rdio-scanner/graphql/types/config.js
Normal file
10
server/lib/rdio-scanner/graphql/types/config.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
type RdioScannerConfig {
|
||||||
|
allowDownload: Boolean
|
||||||
|
useGroup: Boolean
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
module.exports = { schema };
|
||||||
27
server/lib/rdio-scanner/graphql/types/index.js
Normal file
27
server/lib/rdio-scanner/graphql/types/index.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const alias = require('./alias');
|
||||||
|
const callFreq = require('./call-freq');
|
||||||
|
const callQueryResponse = require('./call-query-response');
|
||||||
|
const callSrc = require('./call-src');
|
||||||
|
const call = require('./call');
|
||||||
|
const config = require('./config');
|
||||||
|
const system = require('./system');
|
||||||
|
const talkgroup = require('./talkgroup');
|
||||||
|
|
||||||
|
class Types {
|
||||||
|
constructor() {
|
||||||
|
this.schemas = `
|
||||||
|
${alias.schema}
|
||||||
|
${callFreq.schema}
|
||||||
|
${callQueryResponse.schema}
|
||||||
|
${callSrc.schema}
|
||||||
|
${call.schema}
|
||||||
|
${config.schema}
|
||||||
|
${system.schema}
|
||||||
|
${talkgroup.schema}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Types;
|
||||||
13
server/lib/rdio-scanner/graphql/types/system.js
Normal file
13
server/lib/rdio-scanner/graphql/types/system.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
type RdioScannerSystem {
|
||||||
|
aliases: [RdioScannerAlias]
|
||||||
|
id: String
|
||||||
|
name: String
|
||||||
|
system: Int
|
||||||
|
talkgroups: [RdioScannerTalkgroup]
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
module.exports = { schema };
|
||||||
14
server/lib/rdio-scanner/graphql/types/talkgroup.js
Normal file
14
server/lib/rdio-scanner/graphql/types/talkgroup.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const schema = `
|
||||||
|
type RdioScannerTalkgroup {
|
||||||
|
alphaTag: String
|
||||||
|
dec: Int
|
||||||
|
description: String
|
||||||
|
group: String
|
||||||
|
mode: String
|
||||||
|
tag: String
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
module.exports = { schema };
|
||||||
17
server/lib/rdio-scanner/helpers/get-api-key.js
Normal file
17
server/lib/rdio-scanner/helpers/get-api-key.js
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function getApiKeys() {
|
||||||
|
let apiKeys = ['b29eb8b9-9bcd-4e6e-bb4f-d244ada12736'];
|
||||||
|
|
||||||
|
if (process.env.RDIO_APIKEYS) {
|
||||||
|
try {
|
||||||
|
apiKeys = JSON.parse(process.env.RDIO_APIKEYS);
|
||||||
|
} catch (err) {
|
||||||
|
console.log('ERROR: Invalid JSON format in RDIO_APIKEYS.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getApiKeys;
|
||||||
15
server/lib/rdio-scanner/helpers/parse-date.js
Normal file
15
server/lib/rdio-scanner/helpers/parse-date.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function parseDate(value) {
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return value;
|
||||||
|
} else if (typeof value === 'number') {
|
||||||
|
const date = new Date(1970, 0, 1);
|
||||||
|
date.setUTCSeconds(value - date.getTimezoneOffset() * 60);
|
||||||
|
return date;
|
||||||
|
} else {
|
||||||
|
return new Date(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = parseDate;
|
||||||
27
server/lib/rdio-scanner/helpers/prune-scheduler.js
Normal file
27
server/lib/rdio-scanner/helpers/prune-scheduler.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const PRUNE_DAYS = parseInt(process.env.RDIO_PRUNEDAYS, 10);
|
||||||
|
|
||||||
|
function pruneScheduler(models) {
|
||||||
|
const pruneDays = isNaN(PRUNE_DAYS) ? 7 : PRUNE_DAYS;
|
||||||
|
|
||||||
|
if (models === null || typeof models !== 'object' || pruneDays < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Op = (models.Sequelize && models.Sequelize.Op) || {};
|
||||||
|
|
||||||
|
return setInterval(() => {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
models.rdioScannerCall.destroy({
|
||||||
|
where: {
|
||||||
|
startTime: {
|
||||||
|
[Op.lt]: new Date(now.getFullYear(), now.getMonth(), now.getDate() - pruneDays),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, 15 * 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = pruneScheduler;
|
||||||
54
server/lib/rdio-scanner/helpers/pubsub.js
Normal file
54
server/lib/rdio-scanner/helpers/pubsub.js
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
const EventEmitter = require('events');
|
||||||
|
const { PubSubEngine } = require('apollo-server-express');
|
||||||
|
|
||||||
|
class PubSub extends PubSubEngine {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.eventEmitter = new EventEmitter();
|
||||||
|
|
||||||
|
this.subscriptions = {};
|
||||||
|
|
||||||
|
this.subIdCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async publish(triggerName, payload) {
|
||||||
|
this.eventEmitter.emit(triggerName, payload);
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
async subscribe(triggerName, onMessage) {
|
||||||
|
this.eventEmitter.addListener(triggerName, onMessage);
|
||||||
|
|
||||||
|
this.subIdCounter = ++this.subIdCounter;
|
||||||
|
|
||||||
|
this.subscriptions[this.subIdCounter] = [triggerName, onMessage];
|
||||||
|
|
||||||
|
this.logLiveFeedListeners(triggerName);
|
||||||
|
|
||||||
|
return this.subIdCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
async unsubscribe(subId) {
|
||||||
|
const [triggerName, onMessage] = this.subscriptions[subId];
|
||||||
|
|
||||||
|
delete this.subscriptions[subId];
|
||||||
|
|
||||||
|
this.eventEmitter.removeListener(triggerName, onMessage);
|
||||||
|
|
||||||
|
this.logLiveFeedListeners(triggerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
logLiveFeedListeners(triggerName) {
|
||||||
|
const livefeed = 'rdioScannerCall';
|
||||||
|
|
||||||
|
if (triggerName === livefeed) {
|
||||||
|
const count = Object.keys(this.subscriptions).reduce((c, k) => this.subscriptions[k][0] === livefeed ? ++c : c, 0);
|
||||||
|
|
||||||
|
console.log(`Live feed listeners: ${count}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PubSub;
|
||||||
44
server/lib/rdio-scanner/helpers/validate-api-keys.js
Normal file
44
server/lib/rdio-scanner/helpers/validate-api-keys.js
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const getApiKeys = require('./get-api-key');
|
||||||
|
|
||||||
|
function validateApiKey(key, system, talkgroup) {
|
||||||
|
const apiKeys = getApiKeys();
|
||||||
|
|
||||||
|
if (typeof apiKeys === 'string') {
|
||||||
|
return apiKeys === key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(apiKeys)) {
|
||||||
|
return apiKeys.some((apiKey) => {
|
||||||
|
if (typeof apiKey === 'string') {
|
||||||
|
return apiKey === key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiKey !== null && typeof apiKey === 'object') {
|
||||||
|
return apiKey.key === key && Array.isArray(apiKey.systems) && apiKey.systems.some((sys) => {
|
||||||
|
if (typeof sys === 'number') {
|
||||||
|
return sys === system;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sys !== null && typeof sys === 'object') {
|
||||||
|
if (typeof talkgroup === 'undefined') {
|
||||||
|
return sys.system === +system;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sys.system === +system && Array.isArray(sys.talkgroups) && sys.talkgroups.includes(+talkgroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = validateApiKey;
|
||||||
51
server/lib/rdio-scanner/index.js
Normal file
51
server/lib/rdio-scanner/index.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
|
||||||
|
const config = require('../../config');
|
||||||
|
|
||||||
|
const pruneScheduler = require('./helpers/prune-scheduler');
|
||||||
|
const PubSub = require('./helpers/pubsub');
|
||||||
|
|
||||||
|
const GraphQL = require('./graphql');
|
||||||
|
const Models = require('./models');
|
||||||
|
const Routes = require('./routes');
|
||||||
|
|
||||||
|
const env = process.env.NODE_ENV || 'development';
|
||||||
|
|
||||||
|
class RdioScanner {
|
||||||
|
static get GraphQL() {
|
||||||
|
return GraphQL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get Models() {
|
||||||
|
return Models;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get PubSub() {
|
||||||
|
return PubSub;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get Routes() {
|
||||||
|
return Routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(httpServer, router, pubsub) {
|
||||||
|
this.pubsub = pubsub || new PubSub();
|
||||||
|
|
||||||
|
this.sequelize = new Sequelize(config[env]);
|
||||||
|
|
||||||
|
this.models = new Models(this.sequelize);
|
||||||
|
|
||||||
|
this.graphQL = new GraphQL(this.models, this.pubsub);
|
||||||
|
|
||||||
|
this.graphQL.applyMiddleware({ app: router, cors: true });
|
||||||
|
this.graphQL.installSubscriptionHandlers(httpServer);
|
||||||
|
|
||||||
|
this.routes = new Routes(this.models, this.pubsub, router);
|
||||||
|
|
||||||
|
pruneScheduler(this.models);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = RdioScanner;
|
||||||
|
|
@ -14,24 +14,25 @@ module.exports = {
|
||||||
type: Sequelize.INTEGER,
|
type: Sequelize.INTEGER,
|
||||||
},
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.DATE,
|
type: Sequelize.DATE,
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
updatedAt: {
|
updatedAt: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.DATE,
|
type: Sequelize.DATE,
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.STRING,
|
type: Sequelize.STRING,
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
system: {
|
system: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.INTEGER,
|
type: Sequelize.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
talkgroups: {
|
talkgroups: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.JSON,
|
type: Sequelize.JSON,
|
||||||
|
defaultValue: [],
|
||||||
|
allowNull: false,
|
||||||
}
|
}
|
||||||
}, { transaction });
|
}, { transaction });
|
||||||
|
|
||||||
|
|
@ -8,54 +8,54 @@ module.exports = {
|
||||||
try {
|
try {
|
||||||
await queryInterface.createTable('rdioScannerCalls', {
|
await queryInterface.createTable('rdioScannerCalls', {
|
||||||
id: {
|
id: {
|
||||||
allowNull: false,
|
type: Sequelize.INTEGER,
|
||||||
autoIncrement: true,
|
autoIncrement: true,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
type: Sequelize.INTEGER,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.DATE,
|
type: Sequelize.DATE,
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
updatedAt: {
|
updatedAt: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.DATE,
|
type: Sequelize.DATE,
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
audio: {
|
audio: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.BLOB('long'),
|
type: Sequelize.BLOB('long'),
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
emergency: {
|
emergency: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.BOOLEAN,
|
type: Sequelize.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
freq: {
|
freq: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.INTEGER,
|
type: Sequelize.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
freqList: {
|
freqList: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.JSON,
|
type: Sequelize.JSON,
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
startTime: {
|
startTime: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.DATE,
|
type: Sequelize.DATE,
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
stopTime: {
|
stopTime: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.DATE,
|
type: Sequelize.DATE,
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
srcList: {
|
srcList: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.JSON,
|
type: Sequelize.JSON,
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
system: {
|
system: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.INTEGER,
|
type: Sequelize.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
talkgroup: {
|
talkgroup: {
|
||||||
allowNull: false,
|
|
||||||
type: Sequelize.INTEGER,
|
type: Sequelize.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
},
|
},
|
||||||
}, { transaction });
|
}, { transaction });
|
||||||
|
|
||||||
23
server/lib/rdio-scanner/models/index.js
Normal file
23
server/lib/rdio-scanner/models/index.js
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
|
||||||
|
const rdioScannerCallFactory = require('./rdio-scanner-call');
|
||||||
|
const rdioScannerSystemFactory = require('./rdio-scanner-system');
|
||||||
|
|
||||||
|
class Models {
|
||||||
|
constructor(sequelize) {
|
||||||
|
if (!(sequelize instanceof Sequelize)) {
|
||||||
|
throw new Error('sequelize must be an instance of Sequelize');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Sequelize = Sequelize;
|
||||||
|
|
||||||
|
this.sequelize = sequelize;
|
||||||
|
|
||||||
|
this.rdioScannerCall = rdioScannerCallFactory(sequelize);
|
||||||
|
this.rdioScannerSystem = rdioScannerSystemFactory(sequelize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Models;
|
||||||
84
server/lib/rdio-scanner/models/rdio-scanner-call.js
Normal file
84
server/lib/rdio-scanner/models/rdio-scanner-call.js
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { DataTypes, Model } = require('sequelize');
|
||||||
|
|
||||||
|
class RdioScannerCall extends Model { }
|
||||||
|
|
||||||
|
function rdioScannerCallFactory(sequelize) {
|
||||||
|
RdioScannerCall.init({
|
||||||
|
audio: {
|
||||||
|
type: DataTypes.BLOB('long'),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
emergency: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
defaultValue: false,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
freq: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
freqList: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
defaultValue: [],
|
||||||
|
allowNull: false,
|
||||||
|
get() {
|
||||||
|
let freqList = this.getDataValue('freqList');
|
||||||
|
|
||||||
|
if (typeof freqlist === 'string') {
|
||||||
|
try {
|
||||||
|
freqList = JSON.parse(freqList);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
freqList = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return freqList;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
startTime: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
stopTime: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
srcList: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
defaultValue: [],
|
||||||
|
allowNull: false,
|
||||||
|
get() {
|
||||||
|
let srclist = this.getDataValue('srcList');
|
||||||
|
|
||||||
|
if (typeof srclist === 'string') {
|
||||||
|
try {
|
||||||
|
srclist = JSON.parse(srclist);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
srclist = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return srclist;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
system: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
talkgroup: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
modelName: 'rdioScannerCall',
|
||||||
|
sequelize,
|
||||||
|
});
|
||||||
|
|
||||||
|
return RdioScannerCall;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = rdioScannerCallFactory;
|
||||||
44
server/lib/rdio-scanner/models/rdio-scanner-system.js
Normal file
44
server/lib/rdio-scanner/models/rdio-scanner-system.js
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { DataTypes, Model } = require('sequelize');
|
||||||
|
|
||||||
|
class RdioScannerSystem extends Model { }
|
||||||
|
|
||||||
|
function rdioScannerSystemFactory(sequelize) {
|
||||||
|
RdioScannerSystem.init({
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
system: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
talkgroups: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
defaultValue: [],
|
||||||
|
allowNull: false,
|
||||||
|
get() {
|
||||||
|
let talkgroups = this.getDataValue('talkgroups');
|
||||||
|
|
||||||
|
if (typeof talkgroups === 'string') {
|
||||||
|
try {
|
||||||
|
talkgroups = JSON.parse(talkgroups);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
talkgroups = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return talkgroups;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
modelName: 'rdioScannerSystem',
|
||||||
|
sequelize,
|
||||||
|
});
|
||||||
|
|
||||||
|
return RdioScannerSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = rdioScannerSystemFactory;
|
||||||
16
server/lib/rdio-scanner/routes/index.js
Normal file
16
server/lib/rdio-scanner/routes/index.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const TrunkRecorderCallUpload = require('./trunk-recorder-call-upload');
|
||||||
|
const TrunkRecorderSystemUpload = require('./trunk-recorder-system-upload');
|
||||||
|
|
||||||
|
class Routes {
|
||||||
|
constructor(models, pubsub, router) {
|
||||||
|
this.trunkRecorderCallUpload = new TrunkRecorderCallUpload(models, pubsub);
|
||||||
|
this.trunkRecorderSystemUpload = new TrunkRecorderSystemUpload(models, pubsub);
|
||||||
|
|
||||||
|
router.use(this.trunkRecorderCallUpload.path, this.trunkRecorderCallUpload.middleware);
|
||||||
|
router.use(this.trunkRecorderSystemUpload.path, this.trunkRecorderSystemUpload.middleware);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Routes;
|
||||||
94
server/lib/rdio-scanner/routes/trunk-recorder-call-upload.js
Normal file
94
server/lib/rdio-scanner/routes/trunk-recorder-call-upload.js
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const multer = require('multer');
|
||||||
|
|
||||||
|
const upload = multer({ storage: multer.memoryStorage() });
|
||||||
|
|
||||||
|
const parseDate = require('../helpers/parse-date');
|
||||||
|
const validateApiKey = require('../helpers/validate-api-keys');
|
||||||
|
|
||||||
|
class TrunkRecorderCallUpload {
|
||||||
|
constructor(models, pubsub) {
|
||||||
|
return {
|
||||||
|
path: '/api/trunk-recorder-call-upload',
|
||||||
|
|
||||||
|
middleware: (req, res) => upload.fields([
|
||||||
|
{ name: 'audio', maxCount: 1 },
|
||||||
|
{ name: 'key', maxCount: 1 },
|
||||||
|
{ name: 'meta', maxCount: 1 },
|
||||||
|
{ name: 'system', maxCount: 1 },
|
||||||
|
])(req, res, async (err) => {
|
||||||
|
if (err instanceof multer.MulterError) {
|
||||||
|
res.send(`${err.message}: ${err.field}\n`);
|
||||||
|
|
||||||
|
} else if (err) {
|
||||||
|
res.send(`${err.message}\n`);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const key = req.body.key;
|
||||||
|
|
||||||
|
let meta = req.files.meta[0].buffer.toString();
|
||||||
|
|
||||||
|
try {
|
||||||
|
meta = JSON.parse(meta);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
meta = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(meta.freqList)) {
|
||||||
|
meta.freqList = meta.freqList.map((f) => ({
|
||||||
|
errorCount: f.error_count,
|
||||||
|
freq: f.freq,
|
||||||
|
len: f.len,
|
||||||
|
pos: f.pos,
|
||||||
|
spikeCount: f.spike_count,
|
||||||
|
}));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
meta.freqList = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(meta.srcList)) {
|
||||||
|
meta.srcList = meta.srcList.map((s) => ({
|
||||||
|
src: s.src,
|
||||||
|
pos: s.pos,
|
||||||
|
}));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
meta.srcList = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
audio: req.files.audio[0].buffer,
|
||||||
|
emergency: !!meta.emergency,
|
||||||
|
freq: parseInt(meta.freq, 10),
|
||||||
|
freqList: Array.isArray(meta.freqList) ? meta.freqList : [],
|
||||||
|
startTime: parseDate(meta.start_time),
|
||||||
|
stopTime: parseDate(meta.stop_time),
|
||||||
|
srcList: Array.isArray(meta.srcList) ? meta.srcList : [],
|
||||||
|
system: parseInt(req.body.system, 10),
|
||||||
|
talkgroup: parseInt(meta.talkgroup, 10),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!validateApiKey(key, data.system, data.talkgroup)) {
|
||||||
|
return res.send(`Api key is invalid for system ${data.system} talkgroup ${data.talkgroup}.\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const call = await models.rdioScannerCall.create(data);
|
||||||
|
|
||||||
|
pubsub.publish('rdioScannerCall', { rdioScannerCall: call });
|
||||||
|
|
||||||
|
res.send(`Call imported successfully.\n`);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
res.send(err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TrunkRecorderCallUpload;
|
||||||
103
server/lib/rdio-scanner/routes/trunk-recorder-system-upload.js
Normal file
103
server/lib/rdio-scanner/routes/trunk-recorder-system-upload.js
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
'use script';
|
||||||
|
|
||||||
|
const multer = require('multer');
|
||||||
|
|
||||||
|
const upload = multer({ storage: multer.memoryStorage() });
|
||||||
|
|
||||||
|
const validateApiKey = require('../helpers/validate-api-keys');
|
||||||
|
|
||||||
|
class TrunkRecorderSystemUpload {
|
||||||
|
constructor(models, pubsub) {
|
||||||
|
return {
|
||||||
|
path: '/api/trunk-recorder-system-upload',
|
||||||
|
|
||||||
|
middleware: (req, res) => upload.fields([
|
||||||
|
{ name: 'csv', maxCount: 1 },
|
||||||
|
{ name: 'key', maxCount: 1 },
|
||||||
|
{ name: 'name', maxCount: 1 },
|
||||||
|
{ name: 'system', maxCount: 1 },
|
||||||
|
])(req, res, async (err) => {
|
||||||
|
if (err instanceof multer.MulterError) {
|
||||||
|
res.send(`${err.message}: ${err.field}\n`);
|
||||||
|
|
||||||
|
} else if (err) {
|
||||||
|
res.send(`${err.message}\n`);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const key = req.body.key;
|
||||||
|
|
||||||
|
const system = {
|
||||||
|
name: req.body.name,
|
||||||
|
system: parseInt(req.body.system, 10),
|
||||||
|
talkgroups: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateApiKey(key, system.system)) {
|
||||||
|
return res.send(`Api key is invalid for system ${system.system}.\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const csv = req.files.csv[0].buffer.toString()
|
||||||
|
.split(/\n/)
|
||||||
|
.filter((l) => l.length && !/^\s*#/.test(l));
|
||||||
|
|
||||||
|
for (const line of csv) {
|
||||||
|
const vals = line.split(',');
|
||||||
|
|
||||||
|
const talkgroup = {
|
||||||
|
alphaTag: vals[3],
|
||||||
|
dec: parseInt(vals[0], 10),
|
||||||
|
description: vals[4],
|
||||||
|
group: vals[6],
|
||||||
|
mode: vals[2],
|
||||||
|
tag: vals[5],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (validateApiKey(key, system.system, talkgroup.dec)) {
|
||||||
|
system.talkgroups.push(talkgroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = async () => {
|
||||||
|
const systems = await models.rdioScannerSystem.findAll();
|
||||||
|
|
||||||
|
pubsub.publish('rdioScannerSystems', { rdioScannerSystems: systems });
|
||||||
|
};
|
||||||
|
|
||||||
|
if (system.talkgroups.length) {
|
||||||
|
try {
|
||||||
|
const rec = await models.rdioScannerSystem.findOne({ where: { system: system.system } });
|
||||||
|
|
||||||
|
if (rec) {
|
||||||
|
await rec.update(system);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
await models.rdioScannerSystem.create(system);
|
||||||
|
}
|
||||||
|
|
||||||
|
await emit();
|
||||||
|
|
||||||
|
res.send(`System ${system.name} imported successfully.\n`);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
res.send(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await models.rdioScannerSystem.destroy({ where: { system: system.system } })
|
||||||
|
|
||||||
|
await emit();
|
||||||
|
|
||||||
|
res.send(`System ${system.name} removed from configuration.\n`);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
res.send(`Error while removing system ${system.name}.\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TrunkRecorderSystemUpload;
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
up: async (queryInterface, Sequelize) => {
|
|
||||||
const transaction = await queryInterface.sequelize.transaction();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await queryInterface.addColumn('rdioScannerCalls', 'audioName', Sequelize.STRING, { transaction });
|
|
||||||
|
|
||||||
await queryInterface.addColumn('rdioScannerCalls', 'audioType', Sequelize.STRING, { transaction });
|
|
||||||
|
|
||||||
await queryInterface.addColumn('rdioScannerSystems', 'aliases', Sequelize.JSON, { transaction });
|
|
||||||
|
|
||||||
await transaction.commit();
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
await transaction.rollback();
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
down: async (queryInterface, Sequelize) => {
|
|
||||||
const transaction = queryInterface.sequelize.transaction();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await transaction.commit();
|
|
||||||
|
|
||||||
await queryInterface.removeColumn('rdioScannerCalls', 'audioName', Sequelize.STRING, { transaction });
|
|
||||||
|
|
||||||
await queryInterface.removeColumn('rdioScannerCalls', 'audioType', Sequelize.STRING, { transaction });
|
|
||||||
|
|
||||||
await queryInterface.removeColumn('rdioScannerSystems', 'aliases', Sequelize.JSON, { transaction });
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
await transaction.rollback();
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const camelCase = require('camelcase');
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const Sequelize = require('sequelize');
|
|
||||||
|
|
||||||
const basename = path.basename(__filename);
|
|
||||||
|
|
||||||
const env = process.env.NODE_ENV || 'development';
|
|
||||||
|
|
||||||
const config = require('../config/config.js')[env];
|
|
||||||
|
|
||||||
const db = {};
|
|
||||||
|
|
||||||
const sequelize = new Sequelize(config);
|
|
||||||
|
|
||||||
fs.readdirSync(__dirname).filter(file => {
|
|
||||||
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
|
|
||||||
}).forEach(file => {
|
|
||||||
const model = sequelize['import'](path.join(__dirname, file));
|
|
||||||
const modelName = camelCase(model.name);
|
|
||||||
db[modelName] = model;
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(db).forEach(modelName => {
|
|
||||||
if (db[modelName].associate) {
|
|
||||||
db[modelName].associate(db);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
db.sequelize = sequelize;
|
|
||||||
db.Sequelize = Sequelize;
|
|
||||||
|
|
||||||
module.exports = db;
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports = (sequelize, DataTypes) => {
|
|
||||||
return sequelize.define('rdioScannerCall', {
|
|
||||||
audio: DataTypes.BLOB('long'),
|
|
||||||
audioName: DataTypes.STRING,
|
|
||||||
audioType: DataTypes.STRING,
|
|
||||||
emergency: DataTypes.BOOLEAN,
|
|
||||||
freq: DataTypes.INTEGER,
|
|
||||||
freqList: DataTypes.JSON,
|
|
||||||
startTime: DataTypes.DATE,
|
|
||||||
stopTime: DataTypes.DATE,
|
|
||||||
srcList: DataTypes.JSON,
|
|
||||||
system: DataTypes.INTEGER,
|
|
||||||
talkgroup: DataTypes.INTEGER,
|
|
||||||
}, {});
|
|
||||||
};
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports = (sequelize, DataTypes) => {
|
|
||||||
return sequelize.define('rdioScannerSystem', {
|
|
||||||
aliases: DataTypes.JSON,
|
|
||||||
name: DataTypes.STRING,
|
|
||||||
system: {
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
unique: true,
|
|
||||||
},
|
|
||||||
talkgroups: DataTypes.JSON,
|
|
||||||
}, {});
|
|
||||||
};
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"name": "rdio-scanner-server",
|
"name": "rdio-scanner-server",
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"migrate": "npx sequelize db:migrate",
|
"migrate": "npx sequelize db:migrate --migrations-path lib/rdio-scanner/migrations",
|
||||||
"start": "nodemon index"
|
"start": "nodemon index"
|
||||||
},
|
},
|
||||||
"author": "Chrystian Huot",
|
"author": "Chrystian Huot",
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const mainFile = 'main.html';
|
|
||||||
|
|
||||||
module.exports = ({ clientRoot }) => (req, res) => {
|
|
||||||
if (fs.existsSync(path.join(clientRoot, mainFile))) {
|
|
||||||
return res.sendFile(mainFile, { root: clientRoot });
|
|
||||||
} else {
|
|
||||||
return res.send('A new build of Rdio Scanner is being prepared. Please check back in a few minutes.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const multer = require('multer');
|
|
||||||
const upload = multer({ storage: multer.memoryStorage() });
|
|
||||||
|
|
||||||
const { systemReducer, validateApiKey } = require('../helpers/rdio-scanner');
|
|
||||||
|
|
||||||
module.exports = ({ pubsub, store }) => (req, res) => upload.fields([
|
|
||||||
{ name: 'csv', maxCount: 1 },
|
|
||||||
{ name: 'key', maxCount: 1 },
|
|
||||||
{ name: 'system', maxCount: 1 },
|
|
||||||
])(req, res, (err) => {
|
|
||||||
if (err instanceof multer.MulterError) {
|
|
||||||
res.send(`${err.message}: ${err.field}\n`);
|
|
||||||
|
|
||||||
} else if (err) {
|
|
||||||
res.send(`${err.message}\n`);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const key = req.body.key;
|
|
||||||
|
|
||||||
const system = parseInt(req.body.system, 10);
|
|
||||||
|
|
||||||
const aliases = [];
|
|
||||||
|
|
||||||
if (!validateApiKey(key, system)) {
|
|
||||||
return res.send(`Api key is invalid for system ${system}.\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const csv = req.files.csv[0].buffer.toString()
|
|
||||||
.split(/\n/)
|
|
||||||
.filter((l) => l.length && !/^\s*#/.test(l));
|
|
||||||
|
|
||||||
for (const line of csv) {
|
|
||||||
const vals = line.split(',');
|
|
||||||
|
|
||||||
aliases.push({
|
|
||||||
name: vals[1],
|
|
||||||
uid: parseInt(vals[0], 10),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
store.rdioScannerSystem.findOne({ where: { system } })
|
|
||||||
.then((rdioScannerSystem) => {
|
|
||||||
if (rdioScannerSystem) {
|
|
||||||
rdioScannerSystem.update({ aliases: JSON.stringify(aliases), system }).then(() => {
|
|
||||||
res.send(`System ${rdioScannerSystem.name} updated successfully.\n`);
|
|
||||||
|
|
||||||
store.rdioScannerSystem.findAll({ order: [['system', 'ASC']] }).then((systems) => {
|
|
||||||
pubsub.publish('rdioScannerSystems', { rdioScannerSystems: systemReducer(systems) });
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
res.send(`Cannot update aliases for system ${rdioScannerSystem.name}.\n`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const multer = require('multer');
|
|
||||||
const upload = multer({ storage: multer.memoryStorage() });
|
|
||||||
|
|
||||||
const { callReducer, validateApiKey } = require('../helpers/rdio-scanner');
|
|
||||||
|
|
||||||
module.exports = ({ pubsub, store }) => (req, res) => upload.fields([
|
|
||||||
{ name: 'audio', maxCount: 1 },
|
|
||||||
{ name: 'key', maxCount: 1 },
|
|
||||||
{ name: 'meta', maxCount: 1 },
|
|
||||||
{ name: 'system', maxCount: 1 },
|
|
||||||
])(req, res, (err) => {
|
|
||||||
if (err instanceof multer.MulterError) {
|
|
||||||
res.send(`${err.message}: ${err.field}\n`);
|
|
||||||
|
|
||||||
} else if (err) {
|
|
||||||
res.send(`${err.message}\n`);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const key = req.body.key;
|
|
||||||
|
|
||||||
let meta = req.files.meta[0].buffer.toString();
|
|
||||||
|
|
||||||
try {
|
|
||||||
meta = JSON.parse(meta);
|
|
||||||
} catch (err) {
|
|
||||||
meta = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(meta.freqList)) {
|
|
||||||
meta.freqList = meta.freqList.map((f) => ({
|
|
||||||
errorCount: f.error_count,
|
|
||||||
freq: f.freq,
|
|
||||||
len: f.len,
|
|
||||||
pos: f.pos,
|
|
||||||
spikeCount: f.spike_count,
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
meta.freqList = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(meta.srcList)) {
|
|
||||||
meta.srcList = meta.srcList.map((s) => ({
|
|
||||||
src: s.src,
|
|
||||||
pos: s.pos,
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
meta.srcList = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
audio: req.files.audio[0].buffer,
|
|
||||||
audioName: req.files.audio[0].originalname,
|
|
||||||
audioType: req.files.audio[0].mimetype,
|
|
||||||
emergency: !!meta.emergency,
|
|
||||||
freq: parseInt(meta.freq, 10),
|
|
||||||
freqList: JSON.stringify(Array.isArray(meta.freqList) ? meta.freqList : []),
|
|
||||||
startTime: parseDate(meta.start_time),
|
|
||||||
stopTime: parseDate(meta.stop_time),
|
|
||||||
srcList: JSON.stringify(Array.isArray(meta.srcList) ? meta.srcList : []),
|
|
||||||
system: parseInt(req.body.system, 10),
|
|
||||||
talkgroup: parseInt(meta.talkgroup, 10),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!validateApiKey(key, data.system, data.talkgroup)) {
|
|
||||||
return res.send(`Api key is invalid for system ${data.system} talkgroup ${data.talkgroup}.\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
store.rdioScannerCall.create(data)
|
|
||||||
.then((call) => {
|
|
||||||
pubsub.publish('rdioScannerCall', { rdioScannerCall: callReducer(call) });
|
|
||||||
res.send(`Call imported successfully.\n`)
|
|
||||||
})
|
|
||||||
.catch((err) => res.send(err.message));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function parseDate(value) {
|
|
||||||
if (value instanceof Date) {
|
|
||||||
return value;
|
|
||||||
} else if (typeof value === 'number') {
|
|
||||||
const date = new Date(1970, 0, 1);
|
|
||||||
date.setUTCSeconds(value - date.getTimezoneOffset() * 60);
|
|
||||||
return date;
|
|
||||||
} else {
|
|
||||||
return new Date(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const multer = require('multer');
|
|
||||||
const upload = multer({ storage: multer.memoryStorage() });
|
|
||||||
|
|
||||||
const { systemReducer, validateApiKey } = require('../helpers/rdio-scanner');
|
|
||||||
|
|
||||||
module.exports = ({ pubsub, store }) => (req, res) => upload.fields([
|
|
||||||
{ name: 'csv', maxCount: 1 },
|
|
||||||
{ name: 'key', maxCount: 1 },
|
|
||||||
{ name: 'name', maxCount: 1 },
|
|
||||||
{ name: 'system', maxCount: 1 },
|
|
||||||
])(req, res, (err) => {
|
|
||||||
if (err instanceof multer.MulterError) {
|
|
||||||
res.send(`${err.message}: ${err.field}\n`);
|
|
||||||
|
|
||||||
} else if (err) {
|
|
||||||
res.send(`${err.message}\n`);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const key = req.body.key;
|
|
||||||
|
|
||||||
const system = {
|
|
||||||
name: req.body.name,
|
|
||||||
system: parseInt(req.body.system, 10),
|
|
||||||
talkgroups: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateApiKey(key, system.system)) {
|
|
||||||
return res.send(`Api key is invalid for system ${system.system}.\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const csv = req.files.csv[0].buffer.toString()
|
|
||||||
.split(/\n/)
|
|
||||||
.filter((l) => l.length && !/^\s*#/.test(l));
|
|
||||||
|
|
||||||
for (const line of csv) {
|
|
||||||
const vals = line.split(',');
|
|
||||||
|
|
||||||
const talkgroup = {
|
|
||||||
alphaTag: vals[3],
|
|
||||||
dec: parseInt(vals[0], 10),
|
|
||||||
description: vals[4],
|
|
||||||
group: vals[6],
|
|
||||||
mode: vals[2],
|
|
||||||
tag: vals[5],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (validateApiKey(key, system.system, talkgroup.dec)) {
|
|
||||||
system.talkgroups.push(talkgroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const emit = () => {
|
|
||||||
store.rdioScannerSystem.findAll({ order: [['system', 'ASC']]}).then((systems) => {
|
|
||||||
pubsub.publish('rdioScannerSystems', { rdioScannerSystems: systemReducer(systems) });
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
if (system.talkgroups.length) {
|
|
||||||
system.talkgroups = JSON.stringify(system.talkgroups);
|
|
||||||
|
|
||||||
store.rdioScannerSystem.findOne({ where: { system: system.system } })
|
|
||||||
.then((rdioScannerSystem) => {
|
|
||||||
if (rdioScannerSystem) {
|
|
||||||
rdioScannerSystem.update(system).then(() => {
|
|
||||||
emit();
|
|
||||||
res.send(`System ${system.name} updated successfully.\n`);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
store.rdioScannerSystem.create(system).then(() => {
|
|
||||||
emit();
|
|
||||||
res.send(`System ${system.name} imported successfully.\n`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
store.rdioScannerSystem.destroy({ where: { system: system.system } })
|
|
||||||
.then(() => {
|
|
||||||
emit();
|
|
||||||
res.send(`System ${system.name} removed from configuration.\n`);
|
|
||||||
})
|
|
||||||
.catch(() => res.send(`Error while removing system ${system.name}.\n`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Loading…
Reference in a new issue