diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c210093ae..be997e634 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,32 @@ jobs: - name: Docker Data Task Test run: docker run cloudtak-data:latest npm test + hooks: + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{github.event.pull_request.head.sha || github.sha}} + + - uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: https://registry.npmjs.org/ + + - name: Install + working-directory: ./tasks/hooks/ + run: npm install + + - name: Lint + working-directory: ./tasks/hooks/ + run: npm run lint + + - name: Test + working-directory: ./tasks/hooks/ + run: npm test + pmtiles: runs-on: ubuntu-latest if: github.event.pull_request.draft == false diff --git a/README.md b/README.md index 2bbb74996..dbc44f6e3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,13 @@ ## Installation -Local installation can take advantage of the docker-compose workflow. +Testing locally can be done either running the server directly (recommended for development) or +by running the provided Docker Compose services (recommended for limited testing) + +Note that for full functionality, CloudTAK needs to be deployed into an AWS environment and that +many of the services it provides will initiate AWS API calls with no graceful fallback. + +### Docker Compose ``` docker-compose up --build @@ -15,6 +21,8 @@ docker-compose up --build Once the database and API service have built, the server will start on port 5000. In your webbrowser visit `http://localhost:5000` to view the ETL UI +### Local Development + Installation outside of the docker environment is also fairly straightforward. In the `./api`, perform the following @@ -31,7 +39,7 @@ npm run dev ## AWS Deployment -### Pre-Reqs +### 1. Pre-Reqs The ETL service assumes several pre-requisite dependencies are deployed before initial ETL deployment. @@ -39,11 +47,11 @@ The following are dependencies which need to be created: | Name | Notes | | --------------------- | ----- | -| `coe-vpc-` | VPC & networking to place tasks in - [repo](https://github.com/dfpc-coe/vpc) | -| `coe-ecs-` | ECS Cluster for API Service - [repo](https://github.com/dfpc-coe/ecs) | -| `coe-ecr-etl` | ECR Repository for storing API Images - [repo](https://github.com/dfpc-coe/ecr) | +| `coe-vpc-` | VPC & networking to place tasks in - [repo](https://github.com/dfpc-coe/vpc) | +| `coe-ecs-` | ECS Cluster for API Service - [repo](https://github.com/dfpc-coe/ecs) | +| `coe-ecr-etl` | ECR Repository for storing API Images - [repo](https://github.com/dfpc-coe/ecr) | | `coe-ecr-etl-tasks` | ECR Repository for storing Task Images - [repo](https://github.com/dfpc-coe/ecr) | -| `coe-elb-access` | Centralized ELB Logs - [repo](https://github.com/dfpc-coe/elb-logs) | +| `coe-elb-access` | Centralized ELB Logs - [repo](https://github.com/dfpc-coe/elb-logs) | An AWS ACM certificate must also be generated that covers the subdomain that CloudTAK is deployed to as well as the second level wildcard. Where in the example below CloudTAK is deployed to ie: `map.example.com` The second @@ -55,12 +63,6 @@ IE: *.map.example.com ``` -### Optional Dependencies that can be deployed at any time - -| Name | Notes | -| --------------------- | ----- | -| `coe-media-` | Task Definitions for Media Server Support - [repo](ttps://github.com/dfoc-coe/media-infra) | - **coe-ecr-etl** Can be created using the [dfpc-coe/ecr](https://github.com/dfpc-coe/ecr) repository. @@ -81,20 +83,27 @@ npm install npx deploy create etl-tasks ``` -### S3 Bucket Contents +### 2. Installing Dependencies -An S3 bucket will be created as part of the CloudFormatiom stack that contains geospatial assets -related to user files, missions, CoTs, etc. The following table is an overview of the prefixes -in the bucket and their purpose +From the root directory, install the deploy dependencies -| Prefix | Description | -| ------ | ----------- | -| `attachment/{sha256}/{file.ext}` | CoT Attachments by Data Package reported SHA | -| `data/{data sync id}/{file.ext}` | CloudTAK managed Data Sync file contents | -| `import/{UUID}/{file.ext}` | User Imports | -| `profile/{email}/{file.ext}` | User Files | +```sh +npm install +``` + +### 3. Building Docker Images & Pushing to ECR + +An script to build docker images and publish them to your ECR is provided and can be run using: -### ETL Deployment +``` +npm run build +``` + +from the root of the project. Ensure that you have created the necessary ECR repositories as descrived in the +previos step and that you have AWS credentials provided in your current terminal environment as an `aws ecr get-login-password` +call will be issued. + +### Deployment From the root directory, install the deploy dependencies @@ -144,3 +153,23 @@ Further help about a specific command can be obtained via something like: npx deploy info --help ``` +### Optional Dependencies that can be deployed at any time + +| Name | Notes | +| --------------------- | ----- | +| `coe-media-` | Task Definitions for Media Server Support - [repo](ttps://github.com/dfoc-coe/media-infra) | + + +### S3 Bucket Contents + +An S3 bucket will be created as part of the CloudFormatiom stack that contains geospatial assets +related to user files, missions, CoTs, etc. The following table is an overview of the prefixes +in the bucket and their purpose + +| Prefix | Description | +| ------ | ----------- | +| `attachment/{sha256}/{file.ext}` | CoT Attachments by Data Package reported SHA | +| `data/{data sync id}/{file.ext}` | CloudTAK managed Data Sync file contents | +| `import/{UUID}/{file.ext}` | User Imports | +| `profile/{email}/{file.ext}` | User Files | + diff --git a/api/lib/aws/alarm.ts b/api/lib/aws/alarm.ts index ff33162bf..19f5d2a3b 100644 --- a/api/lib/aws/alarm.ts +++ b/api/lib/aws/alarm.ts @@ -13,7 +13,7 @@ export default class Alarm { } async list(): Promise> { - const cw = new CloudWatch.CloudWatchClient({ region: process.env.AWS_DEFAULT_REGION }); + const cw = new CloudWatch.CloudWatchClient({ region: process.env.AWS_REGION }); try { const map: Map = new Map(); @@ -38,7 +38,7 @@ export default class Alarm { } async get(layer: number): Promise { - const cw = new CloudWatch.CloudWatchClient({ region: process.env.AWS_DEFAULT_REGION }); + const cw = new CloudWatch.CloudWatchClient({ region: process.env.AWS_REGION }); try { const res = await cw.send(new CloudWatch.DescribeAlarmsCommand({ diff --git a/api/lib/aws/batch-logs.ts b/api/lib/aws/batch-logs.ts index 5a4bf2aa7..734868ad0 100644 --- a/api/lib/aws/batch-logs.ts +++ b/api/lib/aws/batch-logs.ts @@ -14,7 +14,7 @@ export type LogGroupOutput = { export default class LogGroup { static async list(stream: string): Promise { try { - const cwl = new CloudWatchLogs.CloudWatchLogsClient({ region: process.env.AWS_DEFAULT_REGION }); + const cwl = new CloudWatchLogs.CloudWatchLogsClient({ region: process.env.AWS_REGION }); const logs = await cwl.send(new CloudWatchLogs.GetLogEventsCommand({ logStreamName: stream, diff --git a/api/lib/aws/batch.ts b/api/lib/aws/batch.ts index 623664d6e..99bf54f87 100644 --- a/api/lib/aws/batch.ts +++ b/api/lib/aws/batch.ts @@ -20,7 +20,7 @@ export interface BatchJob { */ export default class Batch { static async submitImport(config: Config, email: string, id: string, asset: string, task: object = {}): Promise { - const batch = new AWSBatch.BatchClient({ region: process.env.AWS_DEFAULT_REGION }); + const batch = new AWSBatch.BatchClient({ region: process.env.AWS_REGION }); const batchres = await batch.send(new AWSBatch.SubmitJobCommand({ jobName: `import-${id}`, @@ -40,7 +40,7 @@ export default class Batch { } static async submitData(config: Config, data: InferSelectModel, asset: string, task: object = {}): Promise { - const batch = new AWSBatch.BatchClient({ region: process.env.AWS_DEFAULT_REGION }); + const batch = new AWSBatch.BatchClient({ region: process.env.AWS_REGION }); const jobName = `data-${data.id}-${asset.replace(/[^a-zA-Z0-9]/g, '_').slice(0, 50)}`; @@ -64,7 +64,7 @@ export default class Batch { } static async job(config: Config, jobid: string): Promise { - const batch = new AWSBatch.BatchClient({ region: process.env.AWS_DEFAULT_REGION }); + const batch = new AWSBatch.BatchClient({ region: process.env.AWS_REGION }); const res = await batch.send(new AWSBatch.DescribeJobsCommand({ jobs: [jobid] @@ -93,7 +93,7 @@ export default class Batch { } static async list(config: Config, prefix: string): Promise { - const batch = new AWSBatch.BatchClient({ region: process.env.AWS_DEFAULT_REGION }); + const batch = new AWSBatch.BatchClient({ region: process.env.AWS_REGION }); const res = await batch.send(new AWSBatch.ListJobsCommand({ jobQueue: `${config.StackName}-queue`, diff --git a/api/lib/aws/cloudformation.ts b/api/lib/aws/cloudformation.ts index 4ab4517ee..8dfd26c98 100644 --- a/api/lib/aws/cloudformation.ts +++ b/api/lib/aws/cloudformation.ts @@ -12,7 +12,7 @@ export default class CloudFormation { } static async self(config: Config): Promise { - const cf = new AWSCloudFormation.CloudFormationClient({ region: process.env.AWS_DEFAULT_REGION }); + const cf = new AWSCloudFormation.CloudFormationClient({ region: process.env.AWS_REGION }); const res = await cf.send(new AWSCloudFormation.DescribeStacksCommand({ StackName: config.StackName @@ -24,8 +24,8 @@ export default class CloudFormation { } static async create(config: Config, layerid: number, stack: object): Promise { - const cf = new AWSCloudFormation.CloudFormationClient({ region: process.env.AWS_DEFAULT_REGION }); - const cwl = new AWSCWL.CloudWatchLogsClient({ region: process.env.AWS_DEFAULT_REGION }); + const cf = new AWSCloudFormation.CloudFormationClient({ region: process.env.AWS_REGION }); + const cwl = new AWSCWL.CloudWatchLogsClient({ region: process.env.AWS_REGION }); // LogGroups are managed in CloudFormation, if they are present already an error will throw try { @@ -47,7 +47,7 @@ export default class CloudFormation { } static async update(config: Config, layerid: number, stack: object): Promise { - const cf = new AWSCloudFormation.CloudFormationClient({ region: process.env.AWS_DEFAULT_REGION }); + const cf = new AWSCloudFormation.CloudFormationClient({ region: process.env.AWS_REGION }); const arn = await config.fetchArnPrefix('sns'); await cf.send(new AWSCloudFormation.UpdateStackCommand({ @@ -61,7 +61,7 @@ export default class CloudFormation { static async status(config: Config, layerid: number): Promise<{ status: string; }> { - const cf = new AWSCloudFormation.CloudFormationClient({ region: process.env.AWS_DEFAULT_REGION }); + const cf = new AWSCloudFormation.CloudFormationClient({ region: process.env.AWS_REGION }); try { const res = await cf.send(new AWSCloudFormation.DescribeStacksCommand({ @@ -83,7 +83,7 @@ export default class CloudFormation { } static async exists(config: Config, layerid: number): Promise { - const cf = new AWSCloudFormation.CloudFormationClient({ region: process.env.AWS_DEFAULT_REGION }); + const cf = new AWSCloudFormation.CloudFormationClient({ region: process.env.AWS_REGION }); try { await cf.send(new AWSCloudFormation.DescribeStacksCommand({ @@ -101,7 +101,7 @@ export default class CloudFormation { } static async cancel(config: Config, layerid: number): Promise { - const cf = new AWSCloudFormation.CloudFormationClient({ region: process.env.AWS_DEFAULT_REGION }); + const cf = new AWSCloudFormation.CloudFormationClient({ region: process.env.AWS_REGION }); await cf.send(new AWSCloudFormation.CancelUpdateStackCommand({ StackName: this.stdname(config, layerid) @@ -109,7 +109,7 @@ export default class CloudFormation { } static async delete(config: Config, layerid: number): Promise { - const cf = new AWSCloudFormation.CloudFormationClient({ region: process.env.AWS_DEFAULT_REGION }); + const cf = new AWSCloudFormation.CloudFormationClient({ region: process.env.AWS_REGION }); await cf.send(new AWSCloudFormation.DeleteStackCommand({ StackName: this.stdname(config, layerid) diff --git a/api/lib/aws/dynamo.ts b/api/lib/aws/dynamo.ts index 3ded3cd1c..f13427a53 100644 --- a/api/lib/aws/dynamo.ts +++ b/api/lib/aws/dynamo.ts @@ -27,7 +27,7 @@ export default class Dynamo { async query(layerid: number, query: DynamoQuery): Promise { try { - const ddb = new DynamoDB.DynamoDBClient({region: process.env.AWS_DEFAULT_REGION }); + const ddb = new DynamoDB.DynamoDBClient({region: process.env.AWS_REGION }); const ddbdoc = DynamoDBDoc.DynamoDBDocumentClient.from(ddb); let KeyConditionExpression: string = `LayerId = :layerid`; @@ -54,7 +54,7 @@ export default class Dynamo { async row(layerid: number, id: string): Promise { try { - const ddb = new DynamoDB.DynamoDBClient({region: process.env.AWS_DEFAULT_REGION }); + const ddb = new DynamoDB.DynamoDBClient({region: process.env.AWS_REGION }); const ddbdoc = DynamoDBDoc.DynamoDBDocumentClient.from(ddb); const row = await ddbdoc.send(new DynamoDBDoc.GetCommand({ @@ -79,7 +79,7 @@ export default class Dynamo { async put(feature: any): Promise { try { - const ddb = new DynamoDB.DynamoDBClient({region: process.env.AWS_DEFAULT_REGION }); + const ddb = new DynamoDB.DynamoDBClient({region: process.env.AWS_REGION }); const ddbdoc = DynamoDBDoc.DynamoDBDocumentClient.from(ddb); await ddbdoc.send(new DynamoDBDoc.PutCommand({ @@ -99,7 +99,7 @@ export default class Dynamo { async puts(features: any[]): Promise { try { - const ddb = new DynamoDB.DynamoDBClient({region: process.env.AWS_DEFAULT_REGION }); + const ddb = new DynamoDB.DynamoDBClient({region: process.env.AWS_REGION }); const ddbdoc = DynamoDBDoc.DynamoDBDocumentClient.from(ddb); const req: { diff --git a/api/lib/aws/ec2.ts b/api/lib/aws/ec2.ts index 1f7c5152d..51e34ce68 100644 --- a/api/lib/aws/ec2.ts +++ b/api/lib/aws/ec2.ts @@ -8,7 +8,7 @@ import process from 'node:process'; export default class EC2 { static async eni(eni: string): Promise { try { - const ec2 = new AWSEC2.EC2Client({ region: process.env.AWS_DEFAULT_REGION }); + const ec2 = new AWSEC2.EC2Client({ region: process.env.AWS_REGION }); const res = await ec2.send(new AWSEC2.DescribeNetworkInterfacesCommand({ NetworkInterfaceIds: [eni] diff --git a/api/lib/aws/ecr.ts b/api/lib/aws/ecr.ts index 8d53b4710..0c794108b 100644 --- a/api/lib/aws/ecr.ts +++ b/api/lib/aws/ecr.ts @@ -7,7 +7,7 @@ import process from 'node:process'; */ export default class ECR { static async list(): Promise> { - const ecr = new AWSECR.ECRClient({ region: process.env.AWS_DEFAULT_REGION }); + const ecr = new AWSECR.ECRClient({ region: process.env.AWS_REGION }); try { const imageIds: ImageIdentifier[] = []; @@ -27,7 +27,7 @@ export default class ECR { } static async delete(task: string, version: string): Promise { - const ecr = new AWSECR.ECRClient({ region: process.env.AWS_DEFAULT_REGION }); + const ecr = new AWSECR.ECRClient({ region: process.env.AWS_REGION }); try { await ecr.send(new AWSECR.BatchDeleteImageCommand({ diff --git a/api/lib/aws/ecs-video.ts b/api/lib/aws/ecs-video.ts index dee95f45f..52e21ae98 100644 --- a/api/lib/aws/ecs-video.ts +++ b/api/lib/aws/ecs-video.ts @@ -22,7 +22,7 @@ export default class ECSVideo { */ async definitions(): Promise> { try { - const ecs = new AWSECS.ECSClient({ region: process.env.AWS_DEFAULT_REGION }); + const ecs = new AWSECS.ECSClient({ region: process.env.AWS_REGION }); const taskDefinitionArns: string[] = []; let res; @@ -51,7 +51,7 @@ export default class ECSVideo { */ async task(task: string): Promise { try { - const ecs = new AWSECS.ECSClient({ region: process.env.AWS_DEFAULT_REGION }); + const ecs = new AWSECS.ECSClient({ region: process.env.AWS_REGION }); const descs = await ecs.send(new AWSECS.DescribeTasksCommand({ cluster: `coe-ecs-${this.config.StackName.replace(/^coe-etl-/, '')}`, @@ -78,7 +78,7 @@ export default class ECSVideo { } try { - const ecs = new AWSECS.ECSClient({ region: process.env.AWS_DEFAULT_REGION }); + const ecs = new AWSECS.ECSClient({ region: process.env.AWS_REGION }); await ecs.send(new AWSECS.StopTaskCommand({ cluster: `coe-ecs-${this.config.StackName.replace(/^coe-etl-/, '')}`, @@ -95,7 +95,7 @@ export default class ECSVideo { */ async tasks(): Promise> { try { - const ecs = new AWSECS.ECSClient({ region: process.env.AWS_DEFAULT_REGION }); + const ecs = new AWSECS.ECSClient({ region: process.env.AWS_REGION }); const taskArns: string[] = []; let res; @@ -128,7 +128,7 @@ export default class ECSVideo { */ async run(): Promise { try { - const ecs = new AWSECS.ECSClient({ region: process.env.AWS_DEFAULT_REGION }); + const ecs = new AWSECS.ECSClient({ region: process.env.AWS_REGION }); const defs = await this.definitions(); diff --git a/api/lib/aws/hooks.ts b/api/lib/aws/hooks.ts index b41bdda28..0bf82ce14 100644 --- a/api/lib/aws/hooks.ts +++ b/api/lib/aws/hooks.ts @@ -9,7 +9,7 @@ export default class HookQueue { sqs: SQS.SQSClient; constructor() { - this.sqs = new SQS.SQSClient({ region: process.env.AWS_DEFAULT_REGION }); + this.sqs = new SQS.SQSClient({ region: process.env.AWS_REGION }); } async submit( diff --git a/api/lib/aws/lambda-logs.ts b/api/lib/aws/lambda-logs.ts index 097081dae..589899a0b 100644 --- a/api/lib/aws/lambda-logs.ts +++ b/api/lib/aws/lambda-logs.ts @@ -10,7 +10,7 @@ import process from 'node:process'; */ export default class LogGroup { static async delete(config: Config, layer: InferSelectModel): Promise { - const cwl = new CloudWatchLogs.CloudWatchLogsClient({ region: process.env.AWS_DEFAULT_REGION }); + const cwl = new CloudWatchLogs.CloudWatchLogsClient({ region: process.env.AWS_REGION }); await cwl.send(new CloudWatchLogs.DeleteLogGroupCommand({ logGroupName: `/aws/lambda/${config.StackName}-layer-${layer.id}` @@ -23,7 +23,7 @@ export default class LogGroup { timestamp: number; }> }> { - const cwl = new CloudWatchLogs.CloudWatchLogsClient({ region: process.env.AWS_DEFAULT_REGION }); + const cwl = new CloudWatchLogs.CloudWatchLogsClient({ region: process.env.AWS_REGION }); let streams; try { diff --git a/api/lib/aws/lambda.ts b/api/lib/aws/lambda.ts index d519c1eeb..0133fa272 100644 --- a/api/lib/aws/lambda.ts +++ b/api/lib/aws/lambda.ts @@ -12,7 +12,7 @@ import process from 'node:process'; */ export default class Lambda { static async schema(config: Config, layerid: number, type: string = 'schema:input'): Promise { - const lambda = new AWSLambda.LambdaClient({ region: process.env.AWS_DEFAULT_REGION }); + const lambda = new AWSLambda.LambdaClient({ region: process.env.AWS_REGION }); const FunctionName = `${config.StackName}-layer-${layerid}`; const res = await lambda.send(new AWSLambda.InvokeCommand({ @@ -28,7 +28,7 @@ export default class Lambda { } static async invoke(config: Config, layerid: number): Promise { - const lambda = new AWSLambda.LambdaClient({ region: process.env.AWS_DEFAULT_REGION }); + const lambda = new AWSLambda.LambdaClient({ region: process.env.AWS_REGION }); const FunctionName = `${config.StackName}-layer-${layerid}`; await lambda.send(new AWSLambda.InvokeCommand({ diff --git a/api/lib/aws/metric.ts b/api/lib/aws/metric.ts index e16439a2a..7cbb86d9c 100644 --- a/api/lib/aws/metric.ts +++ b/api/lib/aws/metric.ts @@ -15,7 +15,7 @@ export default class Metric { constructor(stack: string) { this.stack = stack; - this.cw = new CloudWatch.CloudWatchClient({ region: process.env.AWS_DEFAULT_REGION }); + this.cw = new CloudWatch.CloudWatchClient({ region: process.env.AWS_REGION }); this.paused = false; } diff --git a/api/lib/aws/s3.ts b/api/lib/aws/s3.ts index 0a6822f4d..3bf8efdc2 100644 --- a/api/lib/aws/s3.ts +++ b/api/lib/aws/s3.ts @@ -12,7 +12,7 @@ export default class S3 { try { if (!process.env.ASSET_BUCKET) throw new Err(400, null, 'ASSET_BUCKET not set'); - const s3 = new S3AWS.S3Client({ region: process.env.AWS_DEFAULT_REGION }); + const s3 = new S3AWS.S3Client({ region: process.env.AWS_REGION }); const head = await s3.send(new S3AWS.HeadObjectCommand({ Bucket: process.env.ASSET_BUCKET, Key: key @@ -28,7 +28,7 @@ export default class S3 { try { if (!process.env.ASSET_BUCKET) throw new Err(400, null, 'ASSET_BUCKET not set'); - const s3 = new S3AWS.S3Client({ region: process.env.AWS_DEFAULT_REGION }); + const s3 = new S3AWS.S3Client({ region: process.env.AWS_REGION }); const upload = new Upload({ client: s3, @@ -49,7 +49,7 @@ export default class S3 { try { if (!process.env.ASSET_BUCKET) throw new Err(400, null, 'ASSET_BUCKET not set'); - const s3 = new S3AWS.S3Client({ region: process.env.AWS_DEFAULT_REGION }); + const s3 = new S3AWS.S3Client({ region: process.env.AWS_REGION }); const res = await s3.send(new S3AWS.GetObjectCommand({ Bucket: process.env.ASSET_BUCKET, @@ -67,7 +67,7 @@ export default class S3 { try { if (!process.env.ASSET_BUCKET) throw new Err(400, null, 'ASSET_BUCKET not set'); - const s3 = new S3AWS.S3Client({ region: process.env.AWS_DEFAULT_REGION }); + const s3 = new S3AWS.S3Client({ region: process.env.AWS_REGION }); await s3.send(new S3AWS.HeadObjectCommand({ Bucket: process.env.ASSET_BUCKET, Key: key @@ -88,7 +88,7 @@ export default class S3 { try { if (!process.env.ASSET_BUCKET) throw new Err(400, null, 'ASSET_BUCKET not set'); - const s3 = new S3AWS.S3Client({ region: process.env.AWS_DEFAULT_REGION }); + const s3 = new S3AWS.S3Client({ region: process.env.AWS_REGION }); const list = await s3.send(new S3AWS.ListObjectsV2Command({ Bucket: process.env.ASSET_BUCKET, Prefix: fragment @@ -111,7 +111,7 @@ export default class S3 { recurse: boolean } = { recurse: false }): Promise { if (!process.env.ASSET_BUCKET) return; - const s3 = new S3AWS.S3Client({ region: process.env.AWS_DEFAULT_REGION }); + const s3 = new S3AWS.S3Client({ region: process.env.AWS_REGION }); if (!opts.recurse) { try { diff --git a/api/lib/config.ts b/api/lib/config.ts index d4c962307..7bffd2cf2 100644 --- a/api/lib/config.ts +++ b/api/lib/config.ts @@ -111,8 +111,8 @@ export default class Config { } static async env(args: ConfigArgs): Promise { - if (!process.env.AWS_DEFAULT_REGION) { - process.env.AWS_DEFAULT_REGION = 'us-east-1'; + if (!process.env.AWS_REGION) { + process.env.AWS_REGION = 'us-east-1'; } let SigningSecret, API_URL, PMTILES_URL, DynamoDB, Bucket, HookURL; @@ -178,7 +178,7 @@ export default class Config { }); if (!config.silent) { - console.error('ok - set env AWS_DEFAULT_REGION: us-east-1'); + console.error('ok - set env AWS_REGION: us-east-1'); console.log(`ok - PMTiles: ${config.PMTILES_URL}`); console.error(`ok - StackName: ${config.StackName}`); } @@ -195,7 +195,7 @@ export default class Config { * Return a prefix to an ARN */ async fetchArnPrefix(service = ''): Promise { - const sts = new STS.STSClient({ region: process.env.AWS_DEFAULT_REGION }); + const sts = new STS.STSClient({ region: process.env.AWS_REGION }); const account = await sts.send(new STS.GetCallerIdentityCommand({})); const res = []; @@ -203,13 +203,13 @@ export default class Config { res.push(...account.Arn.split(':').splice(0, 2)); res.push(service); - res.push(process.env.AWS_DEFAULT_REGION); + res.push(process.env.AWS_REGION); res.push(...account.Arn.split(':').splice(4, 1)) return res.join(':'); } static async fetchSigningSecret(StackName: string): Promise { - const secrets = new SecretsManager.SecretsManagerClient({ region: process.env.AWS_DEFAULT_REGION }); + const secrets = new SecretsManager.SecretsManagerClient({ region: process.env.AWS_REGION }); const secret = await secrets.send(new SecretsManager.GetSecretValueCommand({ SecretId: `${StackName}/api/secret` diff --git a/api/lib/esri.ts b/api/lib/esri.ts index 8aa909bff..00749ae8d 100644 --- a/api/lib/esri.ts +++ b/api/lib/esri.ts @@ -1,7 +1,11 @@ import Err from '@openaddresses/batch-error'; import { Static } from '@sinclair/typebox'; import type { ESRILayerList } from './esri/types.js'; -import { DefaultLayer } from './esri/layer.js' +import { + DefaultLayerPoints, + DefaultLayerLines, + DefaultLayerPolys +} from './esri/layer.js' export enum EsriType { AGOL = 'AGOL', // ArcGIS Online Portal @@ -359,7 +363,7 @@ class EsriProxyServer { } async createLayer(layerDefinition: Static = { - layers: [DefaultLayer] + layers: [DefaultLayerPoints , DefaultLayerLines, DefaultLayerPolys] }): Promise { const url = new URL(this.esri.base) url.pathname = url.pathname.replace(/\/rest/i, '/rest/admin' + this.esri.postfix + '/addToDefinition'); @@ -377,7 +381,7 @@ class EsriProxyServer { const json = await res.json() - if (json.error) throw new Err(400, null, 'ESRI Server Error: ' + json.error.message); + if (json.error) throw new Err(400, new Error(JSON.stringify(json.error)), 'ESRI Server Error: ' + json.error.message); return json; } diff --git a/api/lib/esri/layer.ts b/api/lib/esri/layer.ts index 6cf0ee013..2217ecc77 100644 --- a/api/lib/esri/layer.ts +++ b/api/lib/esri/layer.ts @@ -4,7 +4,11 @@ import type { ESRILayer, ESRIField } from './types.js'; export default class Layer { layers: Array>; - constructor(layers: Array> = [DefaultLayer]) { + constructor(layers: Array> = [ + DefaultLayerPoints, + DefaultLayerLines, + DefaultLayerPolys, + ]) { this.layers = layers; } @@ -18,7 +22,165 @@ export default class Layer { } } -export const DefaultLayer: Static = { + +export const DefaultFields = [{ + "name": "objectid", + "type": "esriFieldTypeOID", + "actualType": "int", + "alias": "fid", + "sqlType": "sqlTypeInteger", + "length": 4, + "nullable": false, + "editable": false, + "domain": null, + "defaultValue": null +},{ + "name": "cotuid", + "type": "esriFieldTypeString", + "actualType": "nvarchar", + "alias": "cotuid1", + "sqlType": "sqlTypeNVarchar", + "length": 100, + "nullable": false, + "editable": true, + "domain": null, + "defaultValue": null +},{ + "name": "remarks", + "type": "esriFieldTypeString", + "actualType": "nvarchar", + "alias": "remarks", + "sqlType": "sqlTypeNVarchar", + "length": 2000, + "nullable": false, + "editable": true, + "domain": null, + "defaultValue": null +},{ + "name": "callsign", + "type": "esriFieldTypeString", + "actualType": "nvarchar", + "alias": "callsign", + "sqlType": "sqlTypeNVarchar", + "length": 100, + "nullable": true, + "editable": true, + "domain": null, + "defaultValue": null +},{ + "name": "type", + "type": "esriFieldTypeString", + "actualType": "nvarchar", + "alias": "type", + "sqlType": "sqlTypeNVarchar", + "length": 100, + "nullable": true, + "editable": true, + "domain": null, + "defaultValue": null +},{ + "name": "how", + "type": "esriFieldTypeString", + "actualType": "nvarchar", + "alias": "how", + "sqlType": "sqlTypeNVarchar", + "length": 100, + "nullable": true, + "editable": true, + "domain": null, + "defaultValue": null +},{ + "name": "time", + "type": "esriFieldTypeDate", + "actualType": "datetime2", + "alias": "time", + "sqlType": "sqlTypeTimestamp2", + "length": 100, + "nullable": true, + "editable": true, + "domain": null, + "defaultValue": null +},{ + "name": "start", + "type": "esriFieldTypeDate", + "actualType": "datetime2", + "alias": "start", + "sqlType": "sqlTypeTimestamp2", + "length": 100, + "nullable": true, + "editable": true, + "domain": null, + "defaultValue": null +},{ + "name": "stale", + "type": "esriFieldTypeDate", + "actualType": "datetime2", + "alias": "stale", + "sqlType": "sqlTypeTimestamp2", + "length": 100, + "nullable": true, + "editable": true, + "domain": null, + "defaultValue": null +}] + +export const DefaultLayerPolys: Static = { + id: 2, + name: 'TAK ETL Polys', + description: 'CoT message Polys', + type: 'Feature Layer', + displayField: 'callsign', + supportedQueryFormats: 'JSON', + capabilities: "Create,Delete,Query,Update,Editing,Extract,Sync", + geometryType: 'esriGeometryPolygon', + allowGeometryUpdates: true, + hasAttachments: false, + hasM: false, + hasZ: false, + objectIdField: 'objectid', + extent: { + xmin: -20037508.34, + ymin: -20048966.1, + xmax: 20037508.34, + ymax: 20048966.1, + spatialReference: { wkid: 102100, latestWkid: 3857 }, + }, + uniqueIdField: { + name: "objectid", + isSystemMaintained: true + }, + fields: DefaultFields +} + +export const DefaultLayerLines: Static = { + id: 1, + name: 'TAK ETL Lines', + description: 'CoT message Lines', + type: 'Feature Layer', + displayField: 'callsign', + supportedQueryFormats: 'JSON', + capabilities: "Create,Delete,Query,Update,Editing,Extract,Sync", + geometryType: 'esriGeometryPolyline', + allowGeometryUpdates: true, + hasAttachments: false, + hasM: false, + hasZ: false, + objectIdField: 'objectid', + extent: { + xmin: -20037508.34, + ymin: -20048966.1, + xmax: 20037508.34, + ymax: 20048966.1, + spatialReference: { wkid: 102100, latestWkid: 3857 }, + }, + uniqueIdField: { + name: "objectid", + isSystemMaintained: true + }, + fields: DefaultFields +} + +export const DefaultLayerPoints: Static = { id: 0, name: 'TAK ETL Points', description: 'CoT message Points', @@ -43,104 +205,5 @@ export const DefaultLayer: Static = { name: "objectid", isSystemMaintained: true }, - fields: [{ - "name": "objectid", - "type": "esriFieldTypeOID", - "actualType": "int", - "alias": "fid", - "sqlType": "sqlTypeInteger", - "length": 4, - "nullable": false, - "editable": false, - "domain": null, - "defaultValue": null - },{ - "name": "cotuid", - "type": "esriFieldTypeString", - "actualType": "nvarchar", - "alias": "cotuid1", - "sqlType": "sqlTypeNVarchar", - "length": 100, - "nullable": false, - "editable": true, - "domain": null, - "defaultValue": null - },{ - "name": "remarks", - "type": "esriFieldTypeString", - "actualType": "nvarchar", - "alias": "remarks", - "sqlType": "sqlTypeNVarchar", - "length": 2000, - "nullable": false, - "editable": true, - "domain": null, - "defaultValue": null - },{ - "name": "callsign", - "type": "esriFieldTypeString", - "actualType": "nvarchar", - "alias": "callsign", - "sqlType": "sqlTypeNVarchar", - "length": 100, - "nullable": true, - "editable": true, - "domain": null, - "defaultValue": null - },{ - "name": "type", - "type": "esriFieldTypeString", - "actualType": "nvarchar", - "alias": "type", - "sqlType": "sqlTypeNVarchar", - "length": 100, - "nullable": true, - "editable": true, - "domain": null, - "defaultValue": null - },{ - "name": "how", - "type": "esriFieldTypeString", - "actualType": "nvarchar", - "alias": "how", - "sqlType": "sqlTypeNVarchar", - "length": 100, - "nullable": true, - "editable": true, - "domain": null, - "defaultValue": null - },{ - "name": "time", - "type": "esriFieldTypeDate", - "actualType": "datetime2", - "alias": "time", - "sqlType": "sqlTypeTimestamp2", - "length": 100, - "nullable": true, - "editable": true, - "domain": null, - "defaultValue": null - },{ - "name": "start", - "type": "esriFieldTypeDate", - "actualType": "datetime2", - "alias": "start", - "sqlType": "sqlTypeTimestamp2", - "length": 100, - "nullable": true, - "editable": true, - "domain": null, - "defaultValue": null - },{ - "name": "stale", - "type": "esriFieldTypeDate", - "actualType": "datetime2", - "alias": "stale", - "sqlType": "sqlTypeTimestamp2", - "length": 100, - "nullable": true, - "editable": true, - "domain": null, - "defaultValue": null - }] + fields: DefaultFields } diff --git a/api/lib/jobs/lambda.js b/api/lib/jobs/lambda.js index 7d1ce2d0e..203d42436 100644 --- a/api/lib/jobs/lambda.js +++ b/api/lib/jobs/lambda.js @@ -3,7 +3,7 @@ import AWSLambda from '@aws-sdk/client-lambda'; import { workerData } from 'node:worker_threads'; try { - const lambda = new AWSLambda.LambdaClient({ region: process.env.AWS_DEFAULT_REGION }); + const lambda = new AWSLambda.LambdaClient({ region: process.env.AWS_REGION }); const FunctionName = `${workerData.StackName}-layer-${workerData.LayerID}`; await lambda.send(new AWSLambda.InvokeCommand({ diff --git a/api/migrations/0066_dusty_earthquake.sql b/api/migrations/0066_dusty_earthquake.sql new file mode 100644 index 000000000..ca495d7eb --- /dev/null +++ b/api/migrations/0066_dusty_earthquake.sql @@ -0,0 +1,7 @@ +-- Custom SQL migration file, put you code below! -- +-- Fixes: https://github.com/drizzle-team/drizzle-orm/issues/724 -- +UPDATE connection_sinks + SET body = ((body #>> '{}')::jsonb); +UPDATE connection_sinks + SET body = (body::JSONB || ('{"points":' || (body->'layer') || '}')::JSONB) - 'layer'; + diff --git a/api/migrations/meta/0066_snapshot.json b/api/migrations/meta/0066_snapshot.json new file mode 100644 index 000000000..a16752051 --- /dev/null +++ b/api/migrations/meta/0066_snapshot.json @@ -0,0 +1,2021 @@ +{ + "id": "65b3e3c9-b394-455e-8ff6-a1dd6201a9f7", + "prevId": "525df272-c75d-47d6-8729-5f992d78017a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.basemaps": { + "name": "basemaps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "overlay": { + "name": "overlay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bounds": { + "name": "bounds", + "type": "GEOMETRY(POLYGON, 4326)", + "primaryKey": false, + "notNull": false + }, + "center": { + "name": "center", + "type": "GEOMETRY(POINT, 4326)", + "primaryKey": false, + "notNull": false + }, + "minzoom": { + "name": "minzoom", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxzoom": { + "name": "maxzoom", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 16 + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'png'" + }, + "style": { + "name": "style", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'zxy'" + }, + "styles": { + "name": "styles", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'[]'::json" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'raster'" + } + }, + "indexes": { + "basemaps_username_idx": { + "name": "basemaps_username_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "basemaps_username_profile_username_fk": { + "name": "basemaps_username_profile_username_fk", + "tableFrom": "basemaps", + "columnsFrom": [ + "username" + ], + "tableTo": "profile", + "columnsTo": [ + "username" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.connections": { + "name": "connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "agency": { + "name": "agency", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auth": { + "name": "auth", + "type": "json", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.connection_sinks": { + "name": "connection_sinks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "connection": { + "name": "connection", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "logging": { + "name": "logging", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "connection_sinks_connection_connections_id_fk": { + "name": "connection_sinks_connection_connections_id_fk", + "tableFrom": "connection_sinks", + "columnsFrom": [ + "connection" + ], + "tableTo": "connections", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.connection_tokens": { + "name": "connection_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "connection": { + "name": "connection", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + } + }, + "indexes": {}, + "foreignKeys": { + "connection_tokens_connection_connections_id_fk": { + "name": "connection_tokens_connection_connections_id_fk", + "tableFrom": "connection_tokens", + "columnsFrom": [ + "connection" + ], + "tableTo": "connections", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.data": { + "name": "data", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "auto_transform": { + "name": "auto_transform", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "mission_sync": { + "name": "mission_sync", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "mission_diff": { + "name": "mission_diff", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "mission_role": { + "name": "mission_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'MISSION_SUBSCRIBER'" + }, + "mission_token": { + "name": "mission_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mission_groups": { + "name": "mission_groups", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "assets": { + "name": "assets", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'[\"*\"]'::json" + }, + "connection": { + "name": "connection", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "data_connection_connections_id_fk": { + "name": "data_connection_connections_id_fk", + "tableFrom": "data", + "columnsFrom": [ + "connection" + ], + "tableTo": "connections", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.icons": { + "name": "icons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "iconset": { + "name": "iconset", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type2525b": { + "name": "type2525b", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "icons_iconset_iconsets_uid_fk": { + "name": "icons_iconset_iconsets_uid_fk", + "tableFrom": "icons", + "columnsFrom": [ + "iconset" + ], + "tableTo": "iconsets", + "columnsTo": [ + "uid" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.iconsets": { + "name": "iconsets", + "schema": "", + "columns": { + "uid": { + "name": "uid", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_group": { + "name": "default_group", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_friendly": { + "name": "default_friendly", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_hostile": { + "name": "default_hostile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_neutral": { + "name": "default_neutral", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_unknown": { + "name": "default_unknown", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "skip_resize": { + "name": "skip_resize", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "iconsets_username_idx": { + "name": "iconsets_username_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "iconsets_username_profile_username_fk": { + "name": "iconsets_username_profile_username_fk", + "tableFrom": "iconsets", + "columnsFrom": [ + "username" + ], + "tableTo": "profile", + "columnsTo": [ + "username" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.imports": { + "name": "imports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Pending'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "batch": { + "name": "batch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Unknown'" + }, + "mode_id": { + "name": "mode_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + } + }, + "indexes": {}, + "foreignKeys": { + "imports_username_profile_username_fk": { + "name": "imports_username_profile_username_fk", + "tableFrom": "imports", + "columnsFrom": [ + "username" + ], + "tableTo": "profile", + "columnsTo": [ + "username" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.layers": { + "name": "layers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'off'" + }, + "alarm_period": { + "name": "alarm_period", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "alarm_evals": { + "name": "alarm_evals", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "alarm_points": { + "name": "alarm_points", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 4 + }, + "alarm_threshold": { + "name": "alarm_threshold", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enabled_styles": { + "name": "enabled_styles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "styles": { + "name": "styles", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "logging": { + "name": "logging", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "stale": { + "name": "stale", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20 + }, + "task": { + "name": "task", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection": { + "name": "connection", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cron": { + "name": "cron", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environment": { + "name": "environment", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "ephemeral": { + "name": "ephemeral", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "config": { + "name": "config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "memory": { + "name": "memory", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 128 + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 128 + }, + "data": { + "name": "data", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"type\":\"object\",\"required\":[],\"properties\":{}}'::json" + } + }, + "indexes": {}, + "foreignKeys": { + "layers_connection_connections_id_fk": { + "name": "layers_connection_connections_id_fk", + "tableFrom": "layers", + "columnsFrom": [ + "connection" + ], + "tableTo": "connections", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "layers_data_data_id_fk": { + "name": "layers_data_data_id_fk", + "tableFrom": "layers", + "columnsFrom": [ + "data" + ], + "tableTo": "data", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "layers_connection_name_unique": { + "name": "layers_connection_name_unique", + "columns": [ + "connection", + "name" + ], + "nullsNotDistinct": false + } + } + }, + "public.layer_alerts": { + "name": "layer_alerts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "layer": { + "name": "layer", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'alert-circle'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'yellow'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Details Unknown'" + }, + "hidden": { + "name": "hidden", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "layer_alerts_layer_layers_id_fk": { + "name": "layer_alerts_layer_layers_id_fk", + "tableFrom": "layer_alerts", + "columnsFrom": [ + "layer" + ], + "tableTo": "layers", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.layers_template": { + "name": "layers_template", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "datasync": { + "name": "datasync", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'off'" + }, + "enabled_styles": { + "name": "enabled_styles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "styles": { + "name": "styles", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "logging": { + "name": "logging", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "stale": { + "name": "stale", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20 + }, + "task": { + "name": "task", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cron": { + "name": "cron", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "memory": { + "name": "memory", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 128 + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 128 + }, + "alarm_period": { + "name": "alarm_period", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "alarm_evals": { + "name": "alarm_evals", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "alarm_points": { + "name": "alarm_points", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 4 + }, + "alarm_threshold": { + "name": "alarm_threshold", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "layers_template_username_profile_username_fk": { + "name": "layers_template_username_profile_username_fk", + "tableFrom": "layers_template", + "columnsFrom": [ + "username" + ], + "tableTo": "profile", + "columnsTo": [ + "username" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.profile": { + "name": "profile", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'Unknown'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "last_login": { + "name": "last_login", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "auth": { + "name": "auth", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "tak_callsign": { + "name": "tak_callsign", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'CloudTAK User'" + }, + "tak_group": { + "name": "tak_group", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Orange'" + }, + "tak_role": { + "name": "tak_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Team Member'" + }, + "tak_loc": { + "name": "tak_loc", + "type": "GEOMETRY(POINT, 4326)", + "primaryKey": false, + "notNull": false + }, + "display_stale": { + "name": "display_stale", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'10 Minutes'" + }, + "display_distance": { + "name": "display_distance", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'mile'" + }, + "display_elevation": { + "name": "display_elevation", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'feet'" + }, + "display_speed": { + "name": "display_speed", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'mi/h'" + }, + "display_text": { + "name": "display_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Medium'" + }, + "system_admin": { + "name": "system_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "agency_admin": { + "name": "agency_admin", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'[]'::json" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.profile_chats": { + "name": "profile_chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chatroom": { + "name": "chatroom", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sender_callsign": { + "name": "sender_callsign", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sender_uid": { + "name": "sender_uid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "profile_chats_username_profile_username_fk": { + "name": "profile_chats_username_profile_username_fk", + "tableFrom": "profile_chats", + "columnsFrom": [ + "username" + ], + "tableTo": "profile", + "columnsTo": [ + "username" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.profile_features": { + "name": "profile_features", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'/'" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "properties": { + "name": "properties", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "geometry": { + "name": "geometry", + "type": "GEOMETRY(GEOMETRYZ, 4326)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "profile_features_username_profile_username_fk": { + "name": "profile_features_username_profile_username_fk", + "tableFrom": "profile_features", + "columnsFrom": [ + "username" + ], + "tableTo": "profile", + "columnsTo": [ + "username" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.profile_missions": { + "name": "profile_missions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "guid": { + "name": "guid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.profile_overlays": { + "name": "profile_overlays", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "pos": { + "name": "pos", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'vector'" + }, + "opacity": { + "name": "opacity", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'1'" + }, + "visible": { + "name": "visible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "styles": { + "name": "styles", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'[]'::json" + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode_id": { + "name": "mode_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "profile_overlays_username_profile_username_fk": { + "name": "profile_overlays_username_profile_username_fk", + "tableFrom": "profile_overlays", + "columnsFrom": [ + "username" + ], + "tableTo": "profile", + "columnsTo": [ + "username" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "profile_overlays_username_url_unique": { + "name": "profile_overlays_username_url_unique", + "columns": [ + "username", + "url" + ], + "nullsNotDistinct": false + } + } + }, + "public.server": { + "name": "server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Default'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth": { + "name": "auth", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "api": { + "name": "api", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "provider_secret": { + "name": "provider_secret", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "provider_client": { + "name": "provider_client", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.spatial_ref_sys": { + "name": "spatial_ref_sys", + "schema": "", + "columns": { + "srid": { + "name": "srid", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "auth_name": { + "name": "auth_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "auth_srid": { + "name": "auth_srid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "srtext": { + "name": "srtext", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": false + }, + "proj4text": { + "name": "proj4text", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo": { + "name": "repo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "readme": { + "name": "readme", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tasks_prefix_unique": { + "name": "tasks_prefix_unique", + "columns": [ + "prefix" + ], + "nullsNotDistinct": false + } + } + }, + "public.tokens": { + "name": "tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.video_lease": { + "name": "video_lease", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "updated": { + "name": "updated", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ephemeral": { + "name": "ephemeral", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "expiration": { + "name": "expiration", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "Now() + INTERVAL 1 HOUR;" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream_user": { + "name": "stream_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_pass": { + "name": "stream_pass", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proxy": { + "name": "proxy", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "video_lease_username_profile_username_fk": { + "name": "video_lease_username_profile_username_fk", + "tableFrom": "video_lease", + "columnsFrom": [ + "username" + ], + "tableTo": "profile", + "columnsTo": [ + "username" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/api/migrations/meta/_journal.json b/api/migrations/meta/_journal.json index 7054dfe08..35987c2ec 100644 --- a/api/migrations/meta/_journal.json +++ b/api/migrations/meta/_journal.json @@ -463,6 +463,13 @@ "when": 1727294631631, "tag": "0065_bumpy_magus", "breakpoints": true + }, + { + "idx": 66, + "version": "7", + "when": 1728402823549, + "tag": "0066_dusty_earthquake", + "breakpoints": true } ] } \ No newline at end of file diff --git a/api/package-lock.json b/api/package-lock.json index 4b99bff01..59138d840 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -27,7 +27,7 @@ "@humanwhocodes/momoa": "^3.0.0", "@maplibre/maplibre-gl-style-spec": "^20.1.1", "@openaddresses/batch-error": "^2.1.4", - "@openaddresses/batch-generic": "^21.0.0", + "@openaddresses/batch-generic": "^22.0.0", "@openaddresses/batch-schema": "^10.9.0", "@openaddresses/cloudfriend": "^7.0.0", "@sinclair/typebox": "^0.33.0", @@ -46,8 +46,8 @@ "connect-history-api-fallback": "^2.0.0", "cors": "^2.8.5", "csv-stringify": "^6.3.0", - "drizzle-kit": "^0.24.0", - "drizzle-orm": "^0.33.0", + "drizzle-kit": "^0.25.0", + "drizzle-orm": "^0.34.0", "drizzle-typebox": "^0.1.1", "express": "^4.18.2", "express-minify": "^1.0.0", @@ -3389,9 +3389,9 @@ } }, "node_modules/@openaddresses/batch-generic": { - "version": "21.2.0", - "resolved": "https://registry.npmjs.org/@openaddresses/batch-generic/-/batch-generic-21.2.0.tgz", - "integrity": "sha512-zNzyZzV/A+aKp0BnKuZDPohEPZI1e5rr4eGE3GD1WisG8OYGQCGeAuEji0Zk+IsthoGEV7DlHIibKvE7+iM0Rw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@openaddresses/batch-generic/-/batch-generic-22.0.0.tgz", + "integrity": "sha512-8tgBvEnyFSCgTEnZwp9Xl4i2loX1x2TuKPcSj3xpRwcK9SrfEBR1Yp4M8+FQi/gJJnrp+5YHMRQRkdYT0/C96g==", "license": "ISC", "dependencies": { "@openaddresses/batch-error": "^2.1.2", @@ -3401,8 +3401,8 @@ "wkx": "^0.5.0" }, "peerDependencies": { - "drizzle-kit": "^0.24.0", - "drizzle-orm": "^0.33.0", + "drizzle-kit": "^0.25.0", + "drizzle-orm": "^0.34.0", "moment": "2.x.x" } }, @@ -6521,9 +6521,9 @@ } }, "node_modules/drizzle-kit": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.24.2.tgz", - "integrity": "sha512-nXOaTSFiuIaTMhS8WJC2d4EBeIcN9OSt2A2cyFbQYBAZbi7lRsVGJNqDpEwPqYfJz38yxbY/UtbvBBahBfnExQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.25.0.tgz", + "integrity": "sha512-Rcf0nYCAKizwjWQCY+d3zytyuTbDb81NcaPor+8NebESlUz1+9W3uGl0+r9FhU4Qal5Zv9j/7neXCSCe7DHzjA==", "license": "MIT", "dependencies": { "@drizzle-team/brocli": "^0.10.1", @@ -6536,15 +6536,15 @@ } }, "node_modules/drizzle-orm": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.33.0.tgz", - "integrity": "sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA==", + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.34.1.tgz", + "integrity": "sha512-t+zCwyWWt8xTqtYV4doE/xYmT7hpv1L8pQ66zddEz+3VWyedBBtctjMAp22mAJPfyWurRQXUJ1nrTtqLq+DqNA==", "license": "Apache-2.0", "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=3", "@electric-sql/pglite": ">=0.1.1", - "@libsql/client": "*", + "@libsql/client": ">=0.10.0", "@neondatabase/serverless": ">=0.1", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", diff --git a/api/package.json b/api/package.json index 4ded5121e..7c11cd98f 100644 --- a/api/package.json +++ b/api/package.json @@ -11,7 +11,11 @@ "lint": "eslint index.ts lib/ routes/ test/", "emptydb": "echo 'DROP DATABASE tak_ps_etl' | psql && echo 'CREATE DATABASE tak_ps_etl' | psql", "loaddb": "npm run emptydb && psql tak_ps_etl < ", - "migratedb": "npm run lint && git stash && tsc --outDir . && drizzle-kit generate && git add migrations/* && git commit -m \"DB Migration\" && git clean -f && git stash", + "migratedb": "npm run migratedb-pre && drizzle-kit generate && npm run migratedb-commit", + "migratedb-custom": "npm run migratedb-pre && drizzle-kit generate --custom && npm run migratedb-commit", + "migratedb-pre": "npm run lint && git stash && tsc --outDir .", + "migratedb-commit": "git add migrations/* && git commit -m 'Custom DB Migration' && git clean -f && git stash", + "dev": "tsx watch --ignore web/ index.ts --noevents --nometrics --nosinks --unsafe", "prod": "NODE_OPTIONS='--max-old-space-size=6144' node dist/index.js" }, @@ -40,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": "^21.0.0", + "@openaddresses/batch-generic": "^22.0.0", "@openaddresses/batch-schema": "^10.9.0", "@openaddresses/cloudfriend": "^7.0.0", "@sinclair/typebox": "^0.33.0", @@ -59,8 +63,8 @@ "connect-history-api-fallback": "^2.0.0", "cors": "^2.8.5", "csv-stringify": "^6.3.0", - "drizzle-kit": "^0.24.0", - "drizzle-orm": "^0.33.0", + "drizzle-kit": "^0.25.0", + "drizzle-orm": "^0.34.0", "drizzle-typebox": "^0.1.1", "express": "^4.18.2", "express-minify": "^1.0.0", diff --git a/api/routes/connection-sink.ts b/api/routes/connection-sink.ts index a70cf476e..2cf51105d 100644 --- a/api/routes/connection-sink.ts +++ b/api/routes/connection-sink.ts @@ -69,7 +69,9 @@ export default async function router(schema: Schema, config: Config) { logging: Type.Boolean(), enabled: Type.Boolean(), body: Type.Object({ - layer: Type.String(), + points: Type.Optional(Type.String()), + lines: Type.Optional(Type.String()), + polys: Type.Optional(Type.String()), url: Type.String(), username: Type.Optional(Type.String()), password: Type.Optional(Type.String()) diff --git a/api/web/src/components/CloudTAK/Menu/Chats.vue b/api/web/src/components/CloudTAK/Menu/Chats.vue index c08446520..bdc4523ae 100644 --- a/api/web/src/components/CloudTAK/Menu/Chats.vue +++ b/api/web/src/components/CloudTAK/Menu/Chats.vue @@ -5,6 +5,8 @@ v-tooltip='"New Chat"' :size='32' :stroke='1' + role='button' + tabindex='0' class='cursor-pointer' @click='$router.push("/menu/contacts")' /> @@ -13,6 +15,8 @@ v-tooltip='"Refresh"' :size='32' :stroke='1' + role='button' + tabindex='0' class='cursor-pointer' @click='fetchList' /> @@ -24,9 +28,14 @@ :create='false' /> @@ -127,7 +186,7 @@