Skip to content

Commit

Permalink
Merge pull request #459 from dfpc-coe/basemap-collections
Browse files Browse the repository at this point in the history
Basemap Collections
  • Loading branch information
ingalls authored Dec 25, 2024
2 parents 7d5e9f8 + e6975a5 commit 93ac5b8
Show file tree
Hide file tree
Showing 69 changed files with 5,781 additions and 4,188 deletions.
4 changes: 3 additions & 1 deletion api/lib/api/groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const GroupListInput = Type.Object({
useCache: Type.Optional(Type.Boolean())
})

export const TAKList_Group = TAKList(Group);

export default class {
api: TAKAPI;

Expand All @@ -25,7 +27,7 @@ export default class {

async list(
query: Static<typeof GroupListInput> = {}
): Promise<TAKList<Static<typeof Group>>> {
): Promise<Static<typeof TAKList_Group>> {
const url = new URL(`/Marti/api/groups/all`, this.api.url);

let q: keyof Static<typeof GroupListInput>;
Expand Down
9 changes: 6 additions & 3 deletions api/lib/api/mission-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export const CreateInput = Type.Object({
creatorUid: Type.String()
})

export const TAKList_MissionLayer = TAKList(MissionLayer);
export const TAKItem_MissionLayer = TAKItem(MissionLayer);

export default class {
api: TAKAPI;

Expand Down Expand Up @@ -124,7 +127,7 @@ export default class {
async list(
name: string,
opts?: Static<typeof MissionOptions>
): Promise<TAKList<Static<typeof MissionLayer>>> {
): Promise<Static<typeof TAKList_MissionLayer>> {
let res;

if (this.#isGUID(name)) {
Expand Down Expand Up @@ -180,7 +183,7 @@ export default class {
name: string,
layerUid: string, // Layer UID
opts?: Static<typeof MissionOptions>
): Promise<TAKItem<Static<typeof MissionLayer>>> {
): Promise<Static<typeof TAKItem_MissionLayer>> {
const layers = await this.list(name, opts);

// TODO this will only return top level layers - need to recurse to lower level layers
Expand All @@ -202,7 +205,7 @@ export default class {
name: string,
query: Static<typeof CreateInput>,
opts?: Static<typeof MissionOptions>
): Promise<TAKItem<Static<typeof MissionLayer>>> {
): Promise<Static<typeof TAKItem_MissionLayer>> {
if (this.#isGUID(name)) {
const url = new URL(`/Marti/api/missions/guid/${this.#encodeName(name)}/layers`, this.api.url);

Expand Down
10 changes: 6 additions & 4 deletions api/lib/api/mission-log.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import TAKAPI from '../tak-api.js';
import { Type, Static } from '@sinclair/typebox';
import type { TAKItem } from './types.js';
import { TAKItem } from './types.js';
import type { MissionOptions } from './mission.js';
import { GUIDMatch } from './mission.js';

Expand All @@ -27,6 +27,8 @@ export const UpdateMissionLog = Type.Composite([ CreateMissionLog, Type.Object({
id: Type.String()
})]);

export const TAKItem_MissionLog = TAKItem(MissionLog);

export default class {
api: TAKAPI;

Expand Down Expand Up @@ -74,7 +76,7 @@ export default class {
*/
async get(
id: string,
): Promise<TAKItem<Static<typeof MissionLog>>> {
): Promise<Static<typeof TAKItem_MissionLog>> {
const url = new URL(`/Marti/api/missions/logs/entries/${encodeURIComponent(id)}`, this.api.url);

return await this.api.fetch(url);
Expand All @@ -89,7 +91,7 @@ export default class {
mission: string,
body: Static<typeof CreateMissionLog>,
opts?: Static<typeof MissionOptions>
): Promise<TAKItem<Static<typeof MissionLog>>> {
): Promise<Static<typeof TAKItem_MissionLog>> {
const url = new URL(`/Marti/api/missions/logs/entries`, this.api.url);

if (this.#isGUID(mission)) {
Expand All @@ -116,7 +118,7 @@ export default class {
mission: string,
body: Static<typeof UpdateMissionLog>,
opts?: Static<typeof MissionOptions>
): Promise<TAKItem<Static<typeof MissionLog>>> {
): Promise<Static<typeof TAKItem_MissionLog>> {
const url = new URL(`/Marti/api/missions/logs/entries`, this.api.url);

if (this.#isGUID(mission)) {
Expand Down
59 changes: 37 additions & 22 deletions api/lib/api/mission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ export enum MissionSubscriberRole {
MISSION_READONLY_SUBSCRIBER = 'MISSION_READONLY_SUBSCRIBER'
}

export const MissionContent = Type.Object({
keywords: Type.Array(Type.String()),
mimeType: Type.String(),
name: Type.String(),
hash: Type.String(),
submissionTime: Type.String(),
submitter: Type.String(),
uid: Type.String(),
creatorUid: Type.String(),
size: Type.Integer(),
expiration: Type.Integer()
});

export const Mission = Type.Object({
name: Type.String(),
description: Type.String(),
Expand Down Expand Up @@ -41,18 +54,7 @@ export const Mission = Type.Object({
contents: Type.Array(Type.Object({
timestamp: Type.String(),
creatorUid: Type.String(),
data: Type.Object({
keywords: Type.Array(Type.String()),
mimeType: Type.String(),
name: Type.String(),
hash: Type.String(),
submissionTime: Type.String(),
submitter: Type.String(),
uid: Type.String(),
creatorUid: Type.String(),
size: Type.Integer(),
expiration: Type.Integer()
})
data: MissionContent
})),
passwordProtected: Type.Boolean(),
token: Type.Optional(Type.String()), // Only present when mission created
Expand All @@ -68,8 +70,16 @@ export const MissionChange = Type.Object({
serverTime: Type.String(),
creatorUid: Type.String(),
contentUid: Type.Optional(Type.String()),
details: Type.Optional(Type.Any()),
contentResource: Type.Optional(Type.Any())
details: Type.Optional(Type.Object({
type: Type.String(),
callsign: Type.String(),
color: Type.Optional(Type.String()),
location: Type.Object({
lat: Type.Number(),
lon: Type.Number()
})
})),
contentResource: Type.Optional(MissionContent)
});

export const MissionRole = Type.Object({
Expand Down Expand Up @@ -174,6 +184,11 @@ export const MissionCreateInput = Type.Object({

export const GUIDMatch = new RegExp(/^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$/);

export const TAKList_Mission = TAKList(Mission);
export const TAKList_MissionChange = TAKList(MissionChange);
export const TAKList_MissionSubscriber = TAKList(MissionSubscriber);
export const TAKItem_MissionSubscriber = TAKItem(MissionSubscriber);

/**
* @class
*/
Expand Down Expand Up @@ -211,7 +226,7 @@ export default class {
name: string,
query: Static<typeof MissionChangesInput>,
opts?: Static<typeof MissionOptions>
): Promise<TAKList<Static<typeof MissionChange>>> {
): Promise<Static<typeof TAKList_MissionChange>> {
if (this.#isGUID(name)) name = (await this.getGuid(name, {})).name;

const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}/changes`, this.api.url);
Expand Down Expand Up @@ -365,7 +380,7 @@ export default class {
async subscriptions(
name: string,
opts?: Static<typeof MissionOptions>
): Promise<TAKItem<Static<typeof MissionSubscriber>>> {
): Promise<Static<typeof TAKItem_MissionSubscriber>> {
const url = this.#isGUID(name)
? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}/subscriptions`, this.api.url)
: new URL(`/Marti/api/missions/${this.#encodeName(name)}/subscriptions`, this.api.url);
Expand All @@ -384,7 +399,7 @@ export default class {
async subscriptionRoles(
name: string,
opts?: Static<typeof MissionOptions>
): Promise<TAKList<Static<typeof MissionSubscriber>>> {
): Promise<Static<typeof TAKList_MissionSubscriber>> {
const url = this.#isGUID(name)
? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}/subscriptions/roles`, this.api.url)
: new URL(`/Marti/api/missions/${this.#encodeName(name)}/subscriptions/roles`, this.api.url);
Expand Down Expand Up @@ -483,7 +498,7 @@ export default class {
name: string,
query: Static<typeof SubscribeInput>,
opts?: Static<typeof MissionOptions>
): Promise<TAKItem<Static<typeof MissionSubscriber>>> {
): Promise<Static<typeof TAKItem_MissionSubscriber>> {
const url = this.#isGUID(name)
? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}/subscription`, this.api.url)
: new URL(`/Marti/api/missions/${this.#encodeName(name)}/subscription`, this.api.url);
Expand Down Expand Up @@ -533,7 +548,7 @@ export default class {
*
* {@link https://docs.tak.gov/api/takserver/redoc#tag/mission-api/operation/getAllMissions_1 TAK Server Docs}.
*/
async list(query: Static<typeof MissionListInput>): Promise<TAKList<Static<typeof Mission>>> {
async list(query: Static<typeof MissionListInput>): Promise<Static<typeof TAKList_Mission>> {
const url = new URL('/Marti/api/missions', this.api.url);

let q: keyof Static<typeof MissionListInput>;
Expand Down Expand Up @@ -567,7 +582,7 @@ export default class {
}
}

const missions: TAKList<Static <typeof Mission>> = await this.api.fetch(url, {
const missions: Static<typeof TAKList_Mission> = await this.api.fetch(url, {
method: 'GET',
headers: this.#headers(opts),
});
Expand All @@ -588,7 +603,7 @@ export default class {
? new URL(`/Marti/api/missions/guid/${encodeURIComponent(name)}`, this.api.url)
: new URL(`/Marti/api/missions/${this.#encodeName(name)}`, this.api.url);

const missions: TAKList<Static<typeof Mission>> = await this.api.fetch(url, {
const missions: Static<typeof TAKList_Mission> = await this.api.fetch(url, {
method: 'GET',
headers: this.#headers(opts),
});
Expand Down Expand Up @@ -622,7 +637,7 @@ export default class {
}
}

const missions: TAKList<Static<typeof Mission>> = await this.api.fetch(url, {
const missions: Static<typeof TAKList_Mission> = await this.api.fetch(url, {
method: 'GET',
headers: this.#headers(opts),
});
Expand Down
5 changes: 4 additions & 1 deletion api/lib/api/subscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export const ListSubscriptionInput = Type.Object({
})
})

export const TAKList_Subscription = TAKList(Subscription);


export default class {
api: TAKAPI;

Expand All @@ -64,7 +67,7 @@ export default class {

async list(
query: Static<typeof ListSubscriptionInput>
): Promise<TAKList<Static<typeof Subscription>>> {
): Promise<Static<typeof TAKList_Subscription>> {
const url = new URL(`/Marti/api/subscriptions/all`, this.api.url);

let q: keyof Static<typeof ListSubscriptionInput>;
Expand Down
24 changes: 13 additions & 11 deletions api/lib/api/types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
export type TAKList<T> = {
version: string;
type: string;
data: Array<T>;
nodeId: string;
}
import { TSchema, Type } from '@sinclair/typebox';

export const TAKItem = <T extends TSchema>(T: T) => {
return Type.Object({
version: Type.String(),
type: Type.String(),
data: T,
messages: Type.Optional(Type.Array(Type.String())),
nodeId: Type.Optional(Type.String())
})
};

export type TAKItem<T> = {
version: string;
type: string;
data: T;
nodeId: string;
export const TAKList = <T extends TSchema>(T: T) => {
return TAKItem(Type.Array(T));
}

export enum TAKGroup {
Expand Down
6 changes: 4 additions & 2 deletions api/lib/geocode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,11 @@ export default class Geocode {
return body.address;
}

async forward(query: string, magicKey: string): Promise<Array<Static<typeof FetchForward>>> {
async forward(query: string, magicKey: string, limit?: number): Promise<Array<Static<typeof FetchForward>>> {
const url = new URL(this.forwardApi)
url.searchParams.append('magicKey', magicKey);
url.searchParams.append('singleLine', query);
if (limit) url.searchParams.append('maxLocations', String(limit));
if (this.token) url.searchParams.append('token', this.token);
url.searchParams.append('f', 'json');

Expand All @@ -84,10 +85,11 @@ export default class Geocode {
return body.candidates;
}

async suggest(query: string): Promise<Array<Static<typeof FetchSuggest>>> {
async suggest(query: string, limit?: number): Promise<Array<Static<typeof FetchSuggest>>> {
const url = new URL(this.suggestApi)
url.searchParams.append('text', query);
url.searchParams.append('f', 'json');
if (limit) url.searchParams.append('maxSuggestions', String(limit));

const res = await fetch(url);

Expand Down
10 changes: 9 additions & 1 deletion api/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ export const ProfileFeature = pgTable('profile_features', {
geometry: geometry({ type: GeometryType.GeometryZ, srid: 4326 }).notNull()
});

export const BasemapCollection = pgTable('basemaps_collection', {
id: serial().primaryKey(),
created: timestamp({ withTimezone: true, mode: 'string' }).notNull().default(sql`Now()`),
updated: timestamp({ withTimezone: true, mode: 'string' }).notNull().default(sql`Now()`),
name: text().notNull(),
})

export const Basemap = pgTable('basemaps', {
id: serial().primaryKey(),
created: timestamp({ withTimezone: true, mode: 'string' }).notNull().default(sql`Now()`),
Expand All @@ -99,6 +106,7 @@ export const Basemap = pgTable('basemaps', {
center: geometry({ type: GeometryType.Point, srid: 4326 }).$type<Point>(),
minzoom: integer().notNull().default(0),
maxzoom: integer().notNull().default(16),
collection: integer().references(() => BasemapCollection.id),
format: text().$type<Basemap_Format>().notNull().default(Basemap_Format.PNG),
style: text().$type<Basemap_Style>().notNull().default(Basemap_Style.ZXY),
styles: json().$type<Array<unknown>>().notNull().default([]),
Expand Down Expand Up @@ -187,7 +195,7 @@ export const ConnectionSink = pgTable('connection_sinks', {
enabled: boolean().notNull().default(true),
connection: integer().notNull().references(() => Connection.id),
type: text().notNull(),
body: json().notNull().default({}),
body: json().$type<Record<string, string>>().notNull().default({}),
logging: boolean().notNull().default(false)
});

Expand Down
7 changes: 6 additions & 1 deletion api/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ export const ConnectionSinkResponse = createSelectSchema(schemas.ConnectionSink,
connection: Type.Integer(),
enabled: Type.Boolean(),
logging: Type.Boolean(),
body: Type.Unknown()
body: Type.Record(Type.String(), Type.String())
});

export const ConnectionResponse = Type.Object({
Expand All @@ -245,9 +245,14 @@ export const ConnectionResponse = Type.Object({
enabled: Type.Boolean(),
});

export const BasemapCollectionResponse = createSelectSchema(schemas.BasemapCollection, {
id: Type.Integer(),
});

export const BasemapResponse = createSelectSchema(schemas.Basemap, {
id: Type.Integer(),
minzoom: Type.Integer(),
maxzoom: Type.Integer(),
styles: Type.Array(Type.Unknown()),
collection: Type.Union([Type.Null(), Type.Integer()]),
});
2 changes: 1 addition & 1 deletion api/migrations/0075_clumsy_alex_power.sql
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ALTER TABLE "profile" ADD COLUMN "tak_loc_freq" integer DEFAULT 2000 NOT NULL;
ALTER TABLE "profile" ADD COLUMN "tak_loc_freq" integer DEFAULT 2000 NOT NULL;
2 changes: 1 addition & 1 deletion api/migrations/0077_cloudy_expediter.sql
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ALTER TABLE "profile" ALTER COLUMN "display_projection" SET DEFAULT 'globe';
ALTER TABLE "profile" ALTER COLUMN "display_projection" SET DEFAULT 'globe';
9 changes: 9 additions & 0 deletions api/migrations/0078_mixed_deathbird.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
CREATE TABLE "basemaps_collection" (
"id" serial PRIMARY KEY NOT NULL,
"created" timestamp with time zone DEFAULT Now() NOT NULL,
"updated" timestamp with time zone DEFAULT Now() NOT NULL,
"name" text NOT NULL
);
--> statement-breakpoint
ALTER TABLE "basemaps" ADD COLUMN "collection" integer;--> statement-breakpoint
ALTER TABLE "basemaps" ADD CONSTRAINT "basemaps_collection_basemaps_collection_id_fk" FOREIGN KEY ("collection") REFERENCES "public"."basemaps_collection"("id") ON DELETE no action ON UPDATE no action;
Loading

0 comments on commit 93ac5b8

Please sign in to comment.