Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mission Layer Content #199

Merged
merged 43 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
6910218
Use Orig Token
ingalls Jun 11, 2024
c04dc31
Update Inex
ingalls Jun 11, 2024
d023047
Add pg-core/index
ingalls Jun 11, 2024
9b49ed6
Rename index
ingalls Jun 11, 2024
6275633
DB Migration
ingalls Jun 11, 2024
5e37213
Add Iconsets
ingalls Jun 11, 2024
5384a38
Alpine3.19
ingalls Jun 11, 2024
f25f656
Use Node Latest
ingalls Jun 11, 2024
fb7b83d
Switch to NodeMon
ingalls Jun 11, 2024
7397f04
NodeMon ignore files in production
ingalls Jun 11, 2024
79c8fe2
[email protected]
ingalls Jun 11, 2024
049db66
Bump Build
ingalls Jun 11, 2024
99fa04c
Update node-cot
ingalls Jun 11, 2024
f7d54c5
Update Deps
ingalls Jun 11, 2024
5077bfa
Update to 4.3.1
ingalls Jun 11, 2024
bdaa707
Lock to 4.1
ingalls Jun 11, 2024
c0d62b2
Lock
ingalls Jun 11, 2024
ca7609b
[email protected]
ingalls Jun 12, 2024
e32c1ee
Add forever
ingalls Jun 12, 2024
ef927b9
Await queue
ingalls Jun 12, 2024
74064a0
Fix Lints
ingalls Jun 12, 2024
8f735bc
Include Subject
ingalls Jun 12, 2024
6d381ed
Update Mission Layer
ingalls Jun 12, 2024
686229a
Ensure Buffer length is less than 1mb
ingalls Jun 12, 2024
75ed17b
Basic Path parsing
ingalls Jun 12, 2024
25239f6
Update Tests
ingalls Jun 12, 2024
80693b8
Update Feature props
ingalls Jun 12, 2024
ee59a3a
Update Feature Type
ingalls Jun 12, 2024
2bad686
Use Proper UID
ingalls Jun 12, 2024
f33eeee
Update Subscription
ingalls Jun 12, 2024
a0a9f70
Rename Mission Contents => Files
ingalls Jun 12, 2024
5adc886
Add Subscription Delete
ingalls Jun 12, 2024
645a5b1
Remove sub
ingalls Jun 12, 2024
1cf6d16
Remove Attach Contents Call
ingalls Jun 12, 2024
4d32ffa
Update Deps
ingalls Jun 12, 2024
3c2205d
Add MissionData Path
ingalls Jun 12, 2024
1abe355
Fix memcached call
ingalls Jun 12, 2024
6971eef
Add Exists check and remove from overlay if Mission no longer exists
ingalls Jun 12, 2024
39cbf1a
Fix race condition when subscribing via UI
ingalls Jun 12, 2024
fa71408
Update Exists function
ingalls Jun 13, 2024
f79bd4c
Rename Mission.exists => Mission.access
ingalls Jun 13, 2024
10ee84a
All Groups should result in db save
ingalls Jun 13, 2024
02b1b47
Fix Lints
ingalls Jun 13, 2024
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
4 changes: 2 additions & 2 deletions api/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
FROM nginx:alpine3.18
FROM nginx:alpine3.19

EXPOSE 5000

ENV HOME=/home/etl
WORKDIR $HOME

RUN apk add nodejs npm memcached python3 make bash g++ openssl postgresql-client
RUN apk add nodejs-current npm memcached python3 make bash g++ openssl postgresql-client

WORKDIR $HOME/api

Expand Down
42 changes: 40 additions & 2 deletions api/lib/api/mission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ export const UnsubscribeInput = Type.Object({
disconnectOnly: Type.Optional(Type.Boolean())
})

export const SubscriptionInput = Type.Object({
uid: Type.String(),
});

export const SubscribeInput = Type.Object({
uid: Type.String(),
password: Type.Optional(Type.String()),
Expand All @@ -133,7 +137,6 @@ export const GetInput = Type.Object({
secago: Type.Optional(Type.Integer()),
start: Type.Optional(Type.String()),
end: Type.Optional(Type.String())

});

export const SetRoleInput = Type.Object({
Expand Down Expand Up @@ -229,7 +232,6 @@ export default class {
let partial = {
event: '',
remainder: await this.latestCots(name, opts),
discard: ''
};

do {
Expand Down Expand Up @@ -431,12 +433,14 @@ export default class {
*/
async subscription(
name: string,
query: Static<typeof SubscriptionInput>,
opts?: Static<typeof MissionOptions>
): Promise<Static<typeof MissionSubscriber>> {
if (this.#isGUID(name)) name = (await this.getGuid(name, {})).name;

const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}/subscription`, this.api.url);

for (const q in query) url.searchParams.append(q, String(query[q]));
const res = await this.api.fetch(url, {
method: 'GET',
headers: this.#headers(opts),
Expand Down Expand Up @@ -523,6 +527,40 @@ export default class {
return missions.data[0];
}

/**
* Check if you have access to a given mission
*/
async access(
name: string,
opts?: Static<typeof MissionOptions>
): Promise<boolean> {
try {
if (this.#isGUID(name)) {
const url = new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}`, this.api.url);

const missions: TAKList<Static<typeof Mission>> = await this.api.fetch(url, {
method: 'GET',
headers: this.#headers(opts),
});

if (!missions.data.length) return false;
return true;
} else {
const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}`, this.api.url);

const missions: TAKList<Static<typeof Mission>> = await this.api.fetch(url, {
method: 'GET',
headers: this.#headers(opts),
});

if (!missions.data.length) return false;
return true;
}
} catch (err) {
return false;
}
}

/**
* Get mission by its Name
*
Expand Down
1 change: 0 additions & 1 deletion api/lib/auth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Request } from 'express';
import { TSchema } from '@sinclair/typebox';
import Err from '@openaddresses/batch-error';
import jwt from 'jsonwebtoken';
import Config from './config.js';
Expand Down
6 changes: 3 additions & 3 deletions api/lib/aws/alarm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ export default class Alarm {
this.stack = stack;
}

async list() {
async list(): Promise<Map<number, string>> {
const cw = new CloudWatch.CloudWatchClient({ region: process.env.AWS_DEFAULT_REGION });

try {
const map = new Map();
const map: Map<number, string> = new Map();
const res = await cw.send(new CloudWatch.DescribeAlarmsCommand({
AlarmNamePrefix: `${this.stack}-layer-`
}));
Expand All @@ -37,7 +37,7 @@ export default class Alarm {
}
}

async get(layer: number) {
async get(layer: number): Promise<string> {
const cw = new CloudWatch.CloudWatchClient({ region: process.env.AWS_DEFAULT_REGION });

try {
Expand Down
6 changes: 3 additions & 3 deletions api/lib/aws/dynamo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ export default class Dynamo {
}
}

#expiry(feature: any) {
#expiry(feature: any): number {
const time = new Date(feature.properties.stale || feature.properties.time || Date.now());
time.setHours(time.getHours() + 24);
return Math.round(time.getTime() / 1000);
}

async put(feature: any) {
async put(feature: any): Promise<void> {
try {
const ddb = new DynamoDB.DynamoDBClient({region: process.env.AWS_DEFAULT_REGION });
const ddbdoc = DynamoDBDoc.DynamoDBDocumentClient.from(ddb);
Expand All @@ -97,7 +97,7 @@ export default class Dynamo {
}
}

async puts(features: any[]) {
async puts(features: any[]): Promise<void> {
try {
const ddb = new DynamoDB.DynamoDBClient({region: process.env.AWS_DEFAULT_REGION });
const ddbdoc = DynamoDBDoc.DynamoDBDocumentClient.from(ddb);
Expand Down
2 changes: 1 addition & 1 deletion api/lib/aws/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default class HookQueue {
this.sqs = new SQS.SQSClient({ region: process.env.AWS_DEFAULT_REGION });
}

async submit(connectionid: number | string, MessageBody: string) {
async submit(connectionid: number | string, MessageBody: string): Promise<SQS.SendMessageCommandOutput> {
try {
const res = await this.sqs.send(new SQS.SendMessageCommand({
QueueUrl: process.env.HookURL,
Expand Down
10 changes: 4 additions & 6 deletions api/lib/cacher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,10 @@ export default class Cacher {

try {
if (key && !this.nocache) {
if (isJSON) {
await this.cache.set(key, JSON.stringify(fresh), {
expires: 604800
});
} else {
await this.cache.set(key, fresh, {
const buff = Buffer.from(isJSON ? JSON.stringify(fresh) : fresh);

if (buff.length < 1000000) {
await this.cache.set(key, buff, {
expires: 604800
});
}
Expand Down
15 changes: 15 additions & 0 deletions api/lib/connection-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Connection } from './schema.js';
import { X509Certificate } from 'crypto';
import { InferSelectModel, sql } from 'drizzle-orm';
import Config from './config.js';

Expand All @@ -21,6 +22,8 @@ export default interface ConnectionConfig {

subscription: (name: string) => Promise<null | MissionSub>;
subscriptions: () => Promise<Array<MissionSub>>;

uid(): string;
}

export class MachineConnConfig implements ConnectionConfig {
Expand All @@ -38,6 +41,14 @@ export class MachineConnConfig implements ConnectionConfig {
this.auth = connection.auth;
}

uid(): string {
const cert = new X509Certificate(this.auth.cert);

const subject = cert.subject.split('\n').reverse().join(',');

return subject;
}

async subscription(name: string): Promise<null | MissionSub> {
const missions = await this.config.models.Data.list({
where: sql`
Expand Down Expand Up @@ -90,6 +101,10 @@ export class ProfileConnConfig implements ConnectionConfig {
this.auth = auth;
}

uid(): string {
return `ANDROID-CloudTAK-${this.id}`;
}

async subscription(name: string): Promise<null | MissionSub> {
const missions = await this.config.models.ProfileOverlay.list({
where: sql`
Expand Down
15 changes: 10 additions & 5 deletions api/lib/connection-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ export default class ConnectionPool extends Map<number | string, ConnectionClien

for (const client of (this.config.wsClients.get(String(conn.id)) || [])) {
if (client.format == 'geojson') {

if (feat.properties && feat.properties.chat) {
client.ws.send(JSON.stringify({ type: 'chat', connection: conn.id, data: feat }));
} else {
Expand Down Expand Up @@ -171,10 +170,16 @@ export default class ConnectionPool extends Map<number | string, ConnectionClien
}).on('secureConnect', async () => {
for (const sub of await connConfig.subscriptions()) {
try {
await api.Mission.subscribe(sub.name, { uid: String(connConfig.id) });

await api.Mission.subscribe(sub.name, {
uid: connConfig.uid()
},{
token: sub.token
});

console.log(`Connection: ${connConfig.id} - Sync: ${sub.name}: Subscribed!`);
} catch (err) {
console.warn(`Connection: ${connConfig.id} - Sync: ${sub.name}: ${err.message}`);
console.warn(`Connection: ${connConfig.id} (${connConfig.uid()}) - Sync: ${sub.name}: ${err.message}`);
}
}
}).on('end', async () => {
Expand Down Expand Up @@ -203,13 +208,13 @@ export default class ConnectionPool extends Map<number | string, ConnectionClien
if (connClient.initial) {
if (connClient.retry >= 5) return; // These are considered stalled connections
connClient.retry++
console.log(`not ok - ${connClient.config.id} - ${connClient.config.name} - retrying in ${connClient.retry * 1000}ms`)
console.log(`not ok - ${connClient.config.uid()} - ${connClient.config.name} - retrying in ${connClient.retry * 1000}ms`)
await sleep(connClient.retry * 1000);
await connClient.tak.reconnect();
} else {
// For now allow infinite retry if a client has connected once
const retryms = Math.min(connClient.retry * 1000, 15000);
console.log(`not ok - ${connClient.config.id} - ${connClient.config.name} - retrying in ${retryms}ms`)
console.log(`not ok - ${connClient.config.uid()} - ${connClient.config.name} - retrying in ${retryms}ms`)
await sleep(retryms);
await connClient.tak.reconnect();
}
Expand Down
2 changes: 1 addition & 1 deletion api/lib/esri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class EsriBase {
let json: { [k: string]: unknown; } = await res.json()

if (json.error) {
// @ts-expect-error
// @ts-expect-error No Typing on JSON Body
throw new Err(400, null, `ESRI Server Error: ${json.error.message} - ${json.error.details.join(', ')}`);
}

Expand Down
3 changes: 1 addition & 2 deletions api/lib/geocode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import fetch from './fetch.js';
import Err from '@openaddresses/batch-error';
import Config from './config.js';
import { Static, Type } from "@sinclair/typebox";

export const FetchReverse = Type.Object({
Expand Down Expand Up @@ -37,7 +36,7 @@ export default class Geocode {
return body.address;
}

async forward(search: string): Promise<Static<typeof FetchForward>> {
async forward(): Promise<Static<typeof FetchForward>> {
throw new Err(400, null, 'Unimplemented');
}
}
1 change: 1 addition & 0 deletions api/lib/jobs/lambda.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* global console, Buffer, process */
import AWSLambda from '@aws-sdk/client-lambda';
import { workerData } from 'node:worker_threads';

Expand Down
1 change: 0 additions & 1 deletion api/lib/provider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Config from './config.js';
import { Type } from '@sinclair/typebox'
import Err from '@openaddresses/batch-error';
import moment from 'moment';
import fetch from './fetch.js';
Expand Down
13 changes: 11 additions & 2 deletions api/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { geometry, GeometryType } from '@openaddresses/batch-generic';
import { ConnectionAuth } from './connection-config.js';
import { TAKGroup, TAKRole } from './api/types.js';
import { Layer_Priority, Profile_Stale, Profile_Speed, Profile_Elevation, Profile_Distance } from './enums.js';
import { json, boolean, integer, timestamp, pgTable, serial, varchar, text, unique } from 'drizzle-orm/pg-core';
import { json, boolean, integer, timestamp, pgTable, serial, varchar, text, unique, index } from 'drizzle-orm/pg-core';

/** Internal Tables for Postgis for use with drizzle-kit push:pg */
export const SpatialRefSys = pgTable('spatial_ref_sys', {
Expand Down Expand Up @@ -73,7 +73,11 @@ export const Basemap = pgTable('basemaps', {
format: text('format').notNull().default('png'),
style: text('style').notNull().default('zxy'),
type: text('type').notNull().default('raster')
});
}, (table) => {
return {
username_idx: index("basemaps_username_idx").on(table.username),
}
})

export const Import = pgTable('imports', {
id: text('id').primaryKey(),
Expand Down Expand Up @@ -102,8 +106,13 @@ export const Iconset = pgTable('iconsets', {
default_neutral: text('default_neutral'),
default_unknown: text('default_unknown'),
skip_resize: boolean('skip_resize').notNull().default(false)
}, (table) => {
return {
username_idx: index("iconsets_username_idx").on(table.username),
}
});


export const Icon = pgTable('icons', {
id: serial('id').primaryKey(),
created: timestamp('created', { withTimezone: true, mode: 'string' }).notNull().default(sql`Now()`),
Expand Down
2 changes: 1 addition & 1 deletion api/lib/sinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default class Sinks extends Map<string, typeof SinkInterface> {
};

try {
this.queue.submit(conn.id, JSON.stringify({
await this.queue.submit(conn.id, JSON.stringify({
id: sink.id,
type: sink.type,
body: sink.body,
Expand Down
4 changes: 1 addition & 3 deletions api/lib/sprites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ export default async function(icons: Array<InferSelectModel<typeof Icon>>, confi
const doc = await SpriteSmith({
src: icons.map((icon) => {
return new Vinyl({
// @ts-ignore
path: config.name ? icon[config.name] + '.png' : icon.path.replace(/.*?\//, ''),
contents: Buffer.from(icon.data, 'base64'),
})
})
});

const coords = {};
const coords: Record<string, any> = {};
for (const key in doc.coordinates) {
// @ts-ignore
coords[key.replace(/.png/, '')] = {
...doc.coordinates[key],
pixelRatio: 1
Expand Down
1 change: 1 addition & 0 deletions api/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export const ConnectionResponse = Type.Object({
status: Type.String(),
agency: Type.Optional(Type.Union([Type.Integer(), Type.Null()])),
certificate: Type.Object({
subject: Type.String(),
validFrom: Type.String(),
validTo: Type.String()
}),
Expand Down
3 changes: 3 additions & 0 deletions api/migrations/0046_premium_newton_destine.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE "layers" ALTER COLUMN "stale" SET DEFAULT 20;--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "basemaps_username_idx" ON "basemaps" USING btree ("username");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "iconsets_username_idx" ON "iconsets" USING btree ("username");
Loading
Loading