diff --git a/api/index.ts b/api/index.ts index 7e09e8656..4707b1148 100644 --- a/api/index.ts +++ b/api/index.ts @@ -211,10 +211,11 @@ export default async function server(config: Config) { throw new Error('Unauthorized'); } } catch (err) { + console.error('Error: WebSocket: ', err); ws.send(JSON.stringify({ type: 'Error', properties: { - message: err instanceof Error ? String(err.message) : String(err) + message: err instanceof Error ? String(err.message) : String(err) } })); await sleep(500); diff --git a/api/lib/api/mission.ts b/api/lib/api/mission.ts index d0c892fce..b5b58ac97 100644 --- a/api/lib/api/mission.ts +++ b/api/lib/api/mission.ts @@ -72,7 +72,7 @@ export default class { } #headers(opts?: Static): object { - if (opts.token) { + if (opts && opts.token) { return { MissionAuthorization: `Bearer ${opts.token}` } diff --git a/api/lib/aws/batch-logs.ts b/api/lib/aws/batch-logs.ts index 8dc5a4578..e54ac1860 100644 --- a/api/lib/aws/batch-logs.ts +++ b/api/lib/aws/batch-logs.ts @@ -31,7 +31,7 @@ export default class LogGroup { }) } } catch (err) { - if (err instanceof Error && err.message.includes('The specified log stream does not exist')) { + if (String(err).startsWith('ResourceNotFoundException')) { return { logs: [] } } else { throw new Error(err instanceof Error ? err.message : String(err)); diff --git a/api/lib/aws/s3.ts b/api/lib/aws/s3.ts index 0f1eb4fec..8d95f9104 100644 --- a/api/lib/aws/s3.ts +++ b/api/lib/aws/s3.ts @@ -75,9 +75,7 @@ export default class S3 { })); return true; } catch (err) { - //@ts-ignore - if (err.code === 'NotFound') return false; - + if (String(err).startsWith('NotFound')) return false; throw new Err(500, new Error(err instanceof Error ? err.message : String(err)), 'Failed to determine existance'); } } diff --git a/api/routes/data-asset.ts b/api/routes/data-asset.ts index 8eff92b72..61d891121 100644 --- a/api/routes/data-asset.ts +++ b/api/routes/data-asset.ts @@ -329,6 +329,10 @@ export default async function router(schema: Schema, config: Config) { const data = await config.models.Data.from(req.params.dataid); + if (!await S3.exists(`data/${req.params.dataid}/${req.params.asset}.pmtiles`)) { + throw new Err(404, null, 'Asset does not exist'); + } + const token = jwt.sign({ access: 'user' }, config.SigningSecret) const url = new URL(`${config.PMTILES_URL}/tiles/data/${data.id}/${req.params.asset}`); url.searchParams.append('token', token); diff --git a/api/routes/profile-asset.ts b/api/routes/profile-asset.ts index 9ebffd4de..db94a9d23 100644 --- a/api/routes/profile-asset.ts +++ b/api/routes/profile-asset.ts @@ -24,7 +24,7 @@ export default async function router(schema: Schema, config: Config) { total: Type.Integer(), tiles: Type.Object({ url: Type.String() - }), + }), assets: Type.Array(ProfileAssetResponse) }) }, async (req, res) => { @@ -128,6 +128,14 @@ export default async function router(schema: Schema, config: Config) { await S3.del(`profile/${user.email}/${req.params.asset}.${req.params.ext}`); + if (await S3.exists(`profile/${user.email}/${req.params.asset}.geojsonld`)) { + await S3.del(`profile/${user.email}/${req.params.asset}.geojsonld`); + } + + if (await S3.exists(`profile/${user.email}/${req.params.asset}.pmtiles`)) { + await S3.del(`profile/${user.email}/${req.params.asset}.pmtiles`); + } + return res.json({ status: 200, message: 'Asset Deleted' @@ -174,6 +182,10 @@ export default async function router(schema: Schema, config: Config) { try { const user = await Auth.as_user(config, req, { token: true }); + if (!await S3.exists(`profile/${user.email}/${req.params.asset}.pmtiles`)) { + throw new Err(404, null, 'Asset does not exist'); + } + const token = jwt.sign({ access: 'profile', email: user.email }, config.SigningSecret) const url = new URL(`${config.PMTILES_URL}/tiles/profile/${user.email}/${req.params.asset}`); url.searchParams.append('token', token); diff --git a/api/routes/profile-overlays.ts b/api/routes/profile-overlays.ts index 351b52ca2..35db52ca6 100644 --- a/api/routes/profile-overlays.ts +++ b/api/routes/profile-overlays.ts @@ -1,6 +1,8 @@ import { Type } from '@sinclair/typebox' +import path from 'node:path'; import Config from '../lib/config.js'; import Schema from '@openaddresses/batch-schema'; +import S3 from '../lib/aws/s3.js'; import Err from '@openaddresses/batch-error'; import Auth, { AuthResource } from '../lib/auth.js'; import { StandardResponse, ProfileOverlayResponse } from '../lib/types.js' @@ -16,12 +18,18 @@ export default async function router(schema: Schema, config: Config) { await schema.get('/profile/overlay', { name: 'Get Overlays', group: 'ProfileOverlays', - description: 'Get User\'s Profile Overlays', + description: ` + Return a list of Profile Overlay's that are curently active. + + Each item is checked to ensure it is still present and if not the overlay is removed from the list + before being returned. + `, query: Type.Object({ limit: Type.Optional(Type.Integer()) }), res: Type.Object({ total: Type.Integer(), + removed: Type.Array(ProfileOverlayResponse), items: Type.Array(ProfileOverlayResponse) }) @@ -33,7 +41,26 @@ export default async function router(schema: Schema, config: Config) { limit: req.query.limit }); - return res.json(overlays); + const removed = []; + + for (let i = 0; i < overlays.items.length; i++) { + const item = overlays.items[i]; + + // TODO someday surface these to the user that the underlying resources don't exist + if ( + (item.mode === 'profile' && !(await S3.exists(`profile/${item.username}/${path.parse(item.url.replace(/\/tile$/, '')).name}.pmtiles`))) + || (item.mode === 'data' && !(await S3.exists(`data/${item.mode_id}/${path.parse(item.url.replace(/\/tile$/, '')).name}.pmtiles`))) + ) { + await config.models.ProfileOverlay.delete(item.id); + removed.push(...overlays.items.splice(i, 1)); + overlays.total--; + } + } + + return res.json({ + removed, + ...overlays + }); } catch (err) { return Err.respond(err, res); } diff --git a/api/web/src/components/CloudTAK/CoTView.vue b/api/web/src/components/CloudTAK/CoTView.vue index 399430fa1..200343ba8 100644 --- a/api/web/src/components/CloudTAK/CoTView.vue +++ b/api/web/src/components/CloudTAK/CoTView.vue @@ -39,7 +39,7 @@ -
+
diff --git a/api/web/src/components/CloudTAK/Menu/Datas.vue b/api/web/src/components/CloudTAK/Menu/Datas.vue index 96725a5a8..d572721f1 100644 --- a/api/web/src/components/CloudTAK/Menu/Datas.vue +++ b/api/web/src/components/CloudTAK/Menu/Datas.vue @@ -23,14 +23,24 @@