Skip to content

Commit

Permalink
Merge pull request #115 from dfpc-coe/latestCoTs-and-changes
Browse files Browse the repository at this point in the history
MissionAPI Improvements & Cert Expiry
  • Loading branch information
ingalls authored Mar 7, 2024
2 parents fbaeb76 + d42bae0 commit 0840809
Show file tree
Hide file tree
Showing 16 changed files with 1,785 additions and 241 deletions.
248 changes: 174 additions & 74 deletions api/lib/api/mission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ export const Mission = Type.Object({
missionChanges: Type.Optional(Type.Array(Type.Unknown())) // Only present on Mission.get()
});

export const MissionChange = Type.Object({
isFederatedChange: Type.Boolean(),
type: Type.String(),
missionName: Type.String(),
timestamp: Type.String(),
serverTime: Type.String(),
creatorUid: Type.String(),
contentUid: Type.Optional(Type.String()),
details: Type.Optional(Type.Any()),
contentResource: Type.Optional(Type.Any())
});

export const MissionSubscriber = Type.Object({
token: Type.Optional(Type.String()),
clientUid: Type.String(),
Expand All @@ -57,6 +69,74 @@ export const AttachContentsInput = Type.Object({
uids: Type.Optional(Type.Array(Type.String())),
});

export const DetachContentsInput = Type.Object({
hash: Type.Optional(Type.String()),
uid: Type.Optional(Type.String())
});

export const ChangesInput = Type.Object({
secago: Type.Optional(Type.Integer()),
start: Type.Optional(Type.String()),
end: Type.Optional(Type.String()),
squashed: Type.Optional(Type.Boolean())
})

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

export const UnsubscribeInput = Type.Object({
uid: Type.String(),
disconnectOnly: Type.Optional(Type.Boolean())
})

export const SubscribeInput = Type.Object({
uid: Type.String(),
password: Type.Optional(Type.String()),
secago: Type.Optional(Type.Integer()),
start: Type.Optional(Type.String()),
end: Type.Optional(Type.String())
})

export const DeleteInput = Type.Object({
creatorUid: Type.Optional(Type.String()),
deepDelete: Type.Optional(Type.Boolean())
})

export const GetInput = Type.Object({
password: Type.Optional(Type.String()),
changes: Type.Optional(Type.Boolean()),
logs: Type.Optional(Type.Boolean()),
secago: Type.Optional(Type.Integer()),
start: Type.Optional(Type.String()),
end: Type.Optional(Type.String())

});

export const ListInput = Type.Object({
passwordProtected: Type.Optional(Type.Boolean()),
defaultRole: Type.Optional(Type.Boolean()),
tool: Type.Optional(Type.String())
});

export const CreateInput = Type.Object({
group: Type.Union([Type.Array(Type.String()), Type.String()]),
creatorUid: Type.String(),
description: Type.Optional(Type.String({ default: '' })),
chatRoom: Type.Optional(Type.String()),
baseLayer: Type.Optional(Type.String()),
bbox: Type.Optional(Type.String()),
boundingPolygon: Type.Optional(Type.Array(Type.String())),
path: Type.Optional(Type.String()),
classification: Type.Optional(Type.String()),
tool: Type.Optional(Type.String({ default: 'public' })),
password: Type.Optional(Type.String()),
defaultRole: Type.Optional(Type.String()),
expiration: Type.Optional(Type.Integer()),
inviteOnly: Type.Optional(Type.Boolean({ default: false })),
allowDupe: Type.Optional(Type.Boolean({ default: false })),
});

/**
* @class
*/
Expand All @@ -81,10 +161,39 @@ export default class {
}
}

async changes(
name: string,
query: Static<typeof ChangesInput>,
opts?: Static<typeof MissionOptions>
): Promise<TAKList<Static<typeof MissionChange>>> {
const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}/changes`, this.api.url);

for (const q in query) url.searchParams.append(q, String(query[q]));
return await this.api.fetch(url, {
method: 'GET',
headers: this.#headers(opts),
});
}

async latestCots(
name: string,
opts?: Static<typeof MissionOptions>
): Promise<string> {
const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}/cot`, this.api.url);

return await this.api.fetch(url, {
method: 'GET',
headers: this.#headers(opts)
});
}

/**
* Return users associated with this mission
*/
async contacts(name: string, opts?: Static<typeof MissionOptions>) {
async contacts(
name: string,
opts?: Static<typeof MissionOptions>
) {
const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}/contacts`, this.api.url);

return await this.api.fetch(url, {
Expand All @@ -96,9 +205,14 @@ export default class {
/**
* Remove a file from the mission
*/
async detachContents(name: string, hash: string, opts?: Static<typeof MissionOptions>) {
async detachContents(
name: string,
body: Static<typeof DetachContentsInput>,
opts?: Static<typeof MissionOptions>
) {
const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}/contents`, this.api.url);
url.searchParams.append('hash', hash);
if (body.hash) url.searchParams.append('hash', body.hash);
if (body.uid) url.searchParams.append('uid', body.uid);

return await this.api.fetch(url, {
method: 'DELETE',
Expand All @@ -109,7 +223,11 @@ export default class {
/**
* Attach a file resource by hash from the TAK Server file manager
*/
async attachContents(name: string, body: Static<typeof AttachContentsInput>, opts?: Static<typeof MissionOptions>) {
async attachContents(
name: string,
body: Static<typeof AttachContentsInput>,
opts?: Static<typeof MissionOptions>
) {
const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}/contents`, this.api.url);

return await this.api.fetch(url, {
Expand All @@ -122,7 +240,12 @@ export default class {
/**
* Upload a Mission Package
*/
async upload(name: string, creatorUid: string, body: Readable, opts?: Static<typeof MissionOptions>) {
async upload(
name: string,
creatorUid: string,
body: Readable,
opts?: Static<typeof MissionOptions>
) {
const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}/contents/missionpackage`, this.api.url);
url.searchParams.append('creatorUid', creatorUid);

Expand All @@ -136,7 +259,10 @@ export default class {
/**
* Return UIDs associated with any subscribed users
*/
async subscriptions(name: string, opts?: Static<typeof MissionOptions>): Promise<TAKList<Static<typeof MissionSubscriber>>> {
async subscriptions(
name: string,
opts?: Static<typeof MissionOptions>
): Promise<TAKList<Static<typeof MissionSubscriber>>> {
const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}/subscriptions`, this.api.url);
return await this.api.fetch(url, {
method: 'GET',
Expand All @@ -147,7 +273,10 @@ export default class {
/**
* Return permissions associated with any subscribed users
*/
async subscriptionRoles(name: string, opts?: Static<typeof MissionOptions>): Promise<TAKList<any>> {
async subscriptionRoles(
name: string,
opts?: Static<typeof MissionOptions>
): Promise<TAKList<any>> {
const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}/subscriptions/roles`, this.api.url);
return await this.api.fetch(url, {
method: 'GET',
Expand All @@ -158,7 +287,10 @@ export default class {
/**
* Return permissions associated with a given mission if subscribed
*/
async subscription(name: string, opts?: Static<typeof MissionOptions>): Promise<Static<typeof MissionSubscriber>> {
async subscription(
name: string,
opts?: Static<typeof MissionOptions>
): Promise<Static<typeof MissionSubscriber>> {
const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}/subscription`, this.api.url);
const res = await this.api.fetch(url, {
method: 'GET',
Expand All @@ -171,15 +303,11 @@ export default class {
/**
* Subscribe to a mission
*/
async subscribe(name: string, query: {
uid: string;
password?: string;
secago?: number;
start?: string;
end?: string;

[key: string]: unknown;
}, opts?: Static<typeof MissionOptions>) {
async subscribe(
name: string,
query: Static<typeof SubscribeInput>,
opts?: Static<typeof MissionOptions>
) {
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]));
Expand All @@ -192,11 +320,11 @@ export default class {
/**
* Get current subscription status
*/
async subscribed(name: string, query: {
uid: string;

[key: string]: unknown;
}, opts?: Static<typeof MissionOptions>) {
async subscribed(
name: string,
query: Static<typeof SubscribedInput>,
opts?: Static<typeof MissionOptions>
) {
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]));
Expand All @@ -209,12 +337,11 @@ export default class {
/**
* Unsubscribe from a mission
*/
async unsubscribe(name: string, query: {
uid: string;
disconnectOnly?: string;

[key: string]: unknown;
}, opts?: Static<typeof MissionOptions>) {
async unsubscribe(
name: string,
query: Static<typeof UnsubscribeInput>,
opts?: Static<typeof MissionOptions>
) {
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]));
Expand Down Expand Up @@ -245,16 +372,11 @@ export default class {
/**
* Get mission by its GUID
*/
async getGuid(guid: string, query: {
password?: string;
changes?: string;
logs?: string;
secago?: string;
start?: string;
end?: string;

[key: string]: unknown;
}, opts?: Static<typeof MissionOptions>): Promise<Static<typeof Mission>> {
async getGuid(
guid: string,
query: Static<typeof GetInput>,
opts?: Static<typeof MissionOptions>
): Promise<Static<typeof Mission>> {
const url = new URL(`/Marti/api/missions/guid/${encodeURIComponent(guid)}`, this.api.url);

for (const q in query) url.searchParams.append(q, String(query[q]));
Expand All @@ -270,17 +392,11 @@ export default class {
/**
* Get mission by its Name
*/
async get(name: string, query: {
password?: string;
changes?: string;
logs?: string;
secago?: string;
start?: string;
end?: string;

[key: string]: unknown;
}, opts?: Static<typeof MissionOptions>): Promise<Static<typeof Mission>> {
name = name.trim();
async get(
name: string,
query: Static<typeof GetInput>,
opts?: Static<typeof MissionOptions>
): Promise<Static<typeof Mission>> {
const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}`, this.api.url);

for (const q in query) url.searchParams.append(q, String(query[q]));
Expand All @@ -297,25 +413,10 @@ export default class {
/**
* Create a new mission
*/
async create(name: string, query: {
group: Array<string> | string;
creatorUid: string;
description?: string;
chatRoom?: string;
baseLayer?: string;
bbox?: string;
boundingPolygon?: string;
path?: string;
classification?: string;
tool?: string;
password?: string;
defaultRole?: string;
expiration?: string;
inviteOnly?: string;
allowDupe?: string;

[key: string]: unknown;
}): Promise<TAKList<Static<typeof Mission>>> {
async create(
name: string,
query: Static<typeof CreateInput>
): Promise<TAKList<Static<typeof Mission>>> {
const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}`, this.api.url);

if (query.group && Array.isArray(query.group)) query.group = query.group.join(',');
Expand All @@ -328,12 +429,11 @@ export default class {
/**
* Delete a mission
*/
async delete(name: string, query: {
creatorUid?: string;
deepDelete?: string;

[key: string]: unknown;
}, opts?: Static<typeof MissionOptions>) {
async delete(
name: string,
query: Static<typeof DeleteInput>,
opts?: Static<typeof MissionOptions>
) {
const url = new URL(`/Marti/api/missions/${this.#encodeName(name)}`, this.api.url);

for (const q in query) url.searchParams.append(q, String(query[q]));
Expand Down
2 changes: 2 additions & 0 deletions api/lib/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default class Models {
Profile: Modeler<typeof pgtypes.Profile>;
ProfileChat: ProfileChat;
ProfileOverlay: Modeler<typeof pgtypes.ProfileOverlay>;
ProfileMission: Modeler<typeof pgtypes.ProfileMission>;

Iconset: Modeler<typeof pgtypes.Iconset>;
Icon: Modeler<typeof pgtypes.Icon>;
Expand All @@ -30,6 +31,7 @@ export default class Models {
this.Server = new Modeler(pg, pgtypes.Server);
this.Profile = new Modeler(pg, pgtypes.Profile);
this.ProfileOverlay = new Modeler(pg, pgtypes.ProfileOverlay);
this.ProfileMission = new Modeler(pg, pgtypes.ProfileMission);
this.Basemap = new Modeler(pg, pgtypes.Basemap);
this.Import = new Modeler(pg, pgtypes.Import);
this.Connection = new Modeler(pg, pgtypes.Connection);
Expand Down
9 changes: 9 additions & 0 deletions api/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,15 @@ export const ConnectionToken = pgTable('connection_tokens', {
updated: timestamp('updated', { withTimezone: true, mode: 'string' }).notNull().default(sql`Now()`),
});

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

export const ProfileOverlay = pgTable('profile_overlays', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
Expand Down
Loading

0 comments on commit 0840809

Please sign in to comment.