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

Feature Tolerance #386

Merged
merged 14 commits into from
Oct 16, 2024
17 changes: 10 additions & 7 deletions api/lib/api/mission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import { TAKItem, TAKList } from './types.js';
import { MissionLog } from './mission-log.js';
import type { Feature } from '@tak-ps/node-cot';

export enum MissionSubscriberRole {
MISSION_OWNER = 'MISSION_OWNER',
MISSION_SUBSCRIBER = 'MISSION_SUBSCRIBER',
MISSION_READONLY_SUBSCRIBER = 'MISSION_READONLY_SUBSCRIBER'
}

export const Mission = Type.Object({
name: Type.String(),
description: Type.String(),
Expand All @@ -23,7 +29,10 @@ export const Mission = Type.Object({
externalData: Type.Array(Type.Unknown()),
feeds: Type.Array(Type.Unknown()),
mapLayers: Type.Array(Type.Unknown()),
ownerRole: Type.Optional(Type.Array(Type.Unknown())),
ownerRole: Type.Optional(Type.Object({
permissions: Type.Array(Type.String()),
type: Type.Enum(MissionSubscriberRole)
})),
inviteOnly: Type.Boolean(),
expiration: Type.Number(),
guid: Type.String(),
Expand Down Expand Up @@ -63,12 +72,6 @@ export const MissionChange = Type.Object({
contentResource: Type.Optional(Type.Any())
});

export enum MissionSubscriberRole {
MISSION_OWNER = 'MISSION_OWNER',
MISSION_SUBSCRIBER = 'MISSION_SUBSCRIBER',
MISSION_READONLY_SUBSCRIBER = 'MISSION_READONLY_SUBSCRIBER'
}

export const MissionRole = Type.Object({
permissions: Type.Array(Type.String()),
hibernateLazyInitializer: Type.Optional(Type.Any()),
Expand Down
6 changes: 3 additions & 3 deletions api/lib/control/video-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,9 @@ export default class VideoServiceControl {

return {
configured: true,
url: video.value,
username: user.value,
password: pass.value,
url: typeof video.value === 'string' ? video.value : '',
username: typeof user.value === 'string' ? user.value : '',
password: typeof pass.value === 'string' ? pass.value : ''
}
}

Expand Down
11 changes: 11 additions & 0 deletions api/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ import { Feature } from '@tak-ps/node-cot';
export const LayerResponse = AugmentedLayer;
export const DataResponse = AugmentedData;

export const LayerError = Type.Object({
error: Type.String(),
feature: Feature.InputFeature
});

export const StandardLayerResponse = Type.Object({
status: Type.Integer(),
message: Type.String(),
errors: Type.Array(LayerError)
});

export const StandardResponse = Type.Object({
status: Type.Integer(),
message: Type.String()
Expand Down
1,496 changes: 1,107 additions & 389 deletions api/package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"@humanwhocodes/momoa": "^3.0.0",
"@maplibre/maplibre-gl-style-spec": "^20.1.1",
"@openaddresses/batch-error": "^2.1.4",
"@openaddresses/batch-generic": "^22.0.0",
"@openaddresses/batch-generic": "^23.0.0",
"@openaddresses/batch-schema": "^10.9.0",
"@openaddresses/cloudfriend": "^7.0.0",
"@sinclair/typebox": "^0.33.0",
Expand All @@ -63,8 +63,8 @@
"connect-history-api-fallback": "^2.0.0",
"cors": "^2.8.5",
"csv-stringify": "^6.3.0",
"drizzle-kit": "^0.25.0",
"drizzle-orm": "^0.34.0",
"drizzle-kit": "^0.26.0",
"drizzle-orm": "^0.35.0",
"drizzle-typebox": "^0.1.1",
"express": "^4.18.2",
"express-minify": "^1.0.0",
Expand Down
6 changes: 3 additions & 3 deletions api/routes/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default async function router(schema: Schema, config: Config) {
return config.models.Setting.from(key);
})))).forEach((k) => {
if (k.status === 'rejected') return;
return final[k.value.key] = k.value.value;
return final[k.value.key] = String(k.value.value);
});

return res.json(final);
Expand Down Expand Up @@ -75,7 +75,7 @@ export default async function router(schema: Schema, config: Config) {
});
}))).forEach((k) => {
if (k.status === 'rejected') return;
return final[k.value.key] = k.value.value;
return final[k.value.key] = String(k.value.value);
});

return res.json(final);
Expand Down Expand Up @@ -118,7 +118,7 @@ export default async function router(schema: Schema, config: Config) {
return config.models.Setting.from(key);
}))).forEach((k) => {
if (k.status === 'rejected') return;
return final[k.value.key] = k.value.value;
return final[k.value.key] = String(k.value.value);
});

for (const group of keys) {
Expand Down
2 changes: 1 addition & 1 deletion api/routes/connection-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export default async function router(schema: Schema, config: Config) {
default: true
}),
mission_groups: Type.Optional(Type.Array(Type.String())),
mission_role: Type.Optional(MissionSubscriberRole)
mission_role: Type.Optional(Type.Enum(MissionSubscriberRole))
}),
res: DataResponse
}, async (req, res) => {
Expand Down
59 changes: 34 additions & 25 deletions api/routes/connection-layer-cot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Style from '../lib/style.js';
import DDBQueue from '../lib/queue.js';
import Config from '../lib/config.js';
import CoT, { Feature } from '@tak-ps/node-cot';
import { StandardResponse } from '../lib/types.js';
import { StandardLayerResponse, LayerError } from '../lib/types.js';
import TAKAPI, { APIAuthCertificate, } from '../lib/tak-api.js';

export default async function router(schema: Schema, config: Config) {
Expand All @@ -30,7 +30,7 @@ export default async function router(schema: Schema, config: Config) {
uids: Type.Optional(Type.Array(Type.String())),
features: Type.Array(Feature.InputFeature)
}),
res: StandardResponse
res: StandardLayerResponse
}, async (req, res) => {
try {
await Auth.as_resource(config, req, {
Expand Down Expand Up @@ -60,24 +60,40 @@ export default async function router(schema: Schema, config: Config) {

let pooledClient;
let data;
const cots = [];

if (!layer.data) {
pooledClient = await config.conns.get(layer.connection);
} else if (layer.data) {
data = await config.models.Data.from(layer.data);

pooledClient = await config.conns.get(data.connection);
if (!pooledClient) throw new Err(500, null, `Pooled Client for ${data.connection} not found in config`);
}

if (!pooledClient || !pooledClient.config || !pooledClient.config.enabled) {
return res.json({ status: 200, message: 'Recieved but Connection Paused', errors: [] });
}

for (const feat of req.body.features) {
const errors: Array<Static<typeof LayerError>> = [];
const cots = [];
for (const feat of req.body.features) {
try {
cots.push(CoT.from_geojson(feat))
} catch (err) {
errors.push({
error: err instanceof Error ? err.message : String(err),
feature: feat
})

console.error(`Failed to decode ${String(err)}: feature: ${JSON.stringify(feat)}`);
}
} else if (layer.data) {
data = await config.models.Data.from(layer.data);
}

if (layer.data && data) {
if (!data.mission_sync) {
return res.status(202).json({ status: 202, message: 'Recieved but Data Mission Sync Disabled' });
return res.status(202).json({ status: 202, message: 'Recieved but Data Mission Sync Disabled', errors });
}

pooledClient = await config.conns.get(data.connection);

if (!pooledClient) throw new Err(500, null, `Pooled Client for ${data.connection} not found in config`);

if (data.mission_diff) {
if (!Array.isArray(req.body.uids)) {
throw new Err(400, null, 'uids Array must be present when submitting to DataSync with MissionDiff');
Expand Down Expand Up @@ -113,32 +129,24 @@ export default async function router(schema: Schema, config: Config) {
existMap.set(String(feat.id), feat);
}

for (const feat of req.body.features) {
const cot = CoT.from_geojson(feat);

const exist = existMap.get(String(feat.id));
for (const cot of cots) {
const exist = existMap.get(cot.uid());
if (exist && data.mission_diff) {
const b = CoT.from_geojson(exist);
if (!cot.isDiff(b)) continue;
}

cot.addDest({ mission: data.name, path: `layer-${layer.id}`, after: '' });
cots.push(cot)
}
} else {
for (const feat of req.body.features) {
const cot = CoT.from_geojson(feat);
for (const cot of cots) {
cot.addDest({ mission: data.name });
cots.push(cot)
}
}
}

if (!pooledClient || !pooledClient.config || !pooledClient.config.enabled) {
return res.json({ status: 200, message: 'Recieved but Connection Paused' });
}

if (cots.length === 0) return res.json({ status: 200, message: 'No features found' });
if (cots.length === 0) return res.json({ status: 200, message: 'No features found', errors });

pooledClient.tak.write(cots);

Expand All @@ -160,8 +168,9 @@ export default async function router(schema: Schema, config: Config) {
}

res.json({
status: 200,
message: 'Submitted'
status: errors.length ? 400 : 200,
message: 'Submitted',
errors
});
} catch (err) {
return Err.respond(err, res);
Expand Down
2 changes: 1 addition & 1 deletion api/routes/marti-mission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export default async function router(schema: Schema, config: Config) {
name: Type.String(),
}),
description: 'Helper API to create a mission',
body: Type.Omit(MissionCreateInput, ['creatorUid', 'ownerRole']),
body: Type.Omit(MissionCreateInput, ['creatorUid']),
res: Mission
}, async (req, res) => {
try {
Expand Down
Loading
Loading