UNPKG

@meshwatch/backend-core

Version:

Meshwatch backend core services.

3 lines (2 loc) 38 kB
import{logger as e}from"@meshwatch/logging";import{DynamoDB as t,SQS as s}from"aws-sdk/clients/all";import{Agent as r,get as o}from"http";import n,{Agent as i}from"https";import a from"@hapi/boom";import{pickBy as c,mapValues as u,get as h,isString as d,isEmpty as m}from"lodash";import{string as l,object as y,date as p,boolean as g,array as w,number as I}from"yup";import{resolveCname as b}from"dns";import{parse as T}from"url";import f from"uuid";import C from"@meshwatch/node-fetch";import{Pool as E}from"pg";import{performance as D}from"perf_hooks";const A=!(!process.env.LAMBDA_TASK_ROOT&&!process.env.AWS_EXECUTION_ENV),S="true"===(process.env.IS_LOCAL||"false"),K="true"===(process.env.IS_OFFLINE||"false"),N=A&&!S&&!K,x=/^https:\/\/dynamodb\.((us(-gov)?|ap|ca|cn|eu|sa)-(central|(north|south)?(east|west)?)-\d).amazonaws\.com$/;function k(e){const{AWS_REGION:t,AWS_ACCESS_KEY_ID:s,AWS_SECRET_ACCESS_KEY:o}=process.env;let n=t,a=void 0,c=void 0;N||(n=n||"us-east-1",a=s||"accessKeyId",c=o||"secretAccessKey");const u={keepAlive:!0,maxSockets:50},h=(d=e.endpoint,!d||x.test(d)?new i({...u,rejectUnauthorized:!0}):new r(u));var d;return{region:n,accessKeyId:a,secretAccessKey:c,httpOptions:{agent:h},...e}}function U(e={}){const{AWS_DYNAMODB_ENDPOINT:t}=process.env;let s=e.endpoint||t;return N||s||(s="http://localhost:8000"),k({...e,endpoint:s})}class M{constructor(e,t){this.tableName=e,this.dynamoDbClient=t}createTable(e){return this.dynamoDbClient.createTable({...e,TableName:this.tableName})}putDocumentToDynamo(e){return this.dynamoDbClient.putDocumentToDynamo({...e,TableName:this.tableName})}queryTable(e){return this.dynamoDbClient.queryTable({...e,TableName:this.tableName})}scanTable(e){return this.dynamoDbClient.scanTable({...e,TableName:this.tableName})}deleteItem(e){return this.dynamoDbClient.deleteItem({...e,TableName:this.tableName})}updateItem(e){return this.dynamoDbClient.updateItem({...e,TableName:this.tableName})}}class R{constructor(s){this.createTable=(t=>this.dynamodb.createTable(t).promise().then(e=>e).catch(s=>{throw e.error("[DynamoDB]: Unable to create table",s,{createTableParams:t}),s})),this.transactWriteItems=(t=>this.dynamodbDocumentClient.transactWrite(t).promise().catch(s=>{throw e.error("[DynamoDB] transactWriteItems failed",s,{transactWriteParams:t}),s})),this.putDocumentToDynamo=(t=>{const{TableName:s}=t;return this.dynamodbDocumentClient.put(t).promise().catch(r=>{throw e.error(`[DynamoDB] put to table [${s}] failed`,r,{putItemParams:t}),r})}),this.queryTable=(t=>{const{TableName:s}=t;return this.dynamodbDocumentClient.query(t).promise().catch(r=>{throw e.error(`[DynamoDb] query to table [${s}] failed`,r,{queryTableParams:t}),r})}),this.scanTable=(t=>{const{TableName:s}=t;return this.dynamodbDocumentClient.scan(t).promise().catch(r=>{throw e.error(`[DynamoDb] scan to table [${s}] failed`,r,{scanTableParams:t}),r})}),this.deleteItem=(t=>this.dynamodbDocumentClient.delete(t).promise().catch(s=>{throw e.error("[DynamoDB]: Unable to delete item",s,{deleteItemParams:t}),s})),this.updateItem=(t=>this.dynamodbDocumentClient.update(t).promise().catch(s=>{throw e.error("[DynamoDB]: Unable to update item",s,{updateItemParams:t}),s}));const r=U(s);this.dynamodb=new t(r),this.dynamodbDocumentClient=new t.DocumentClient({...r,convertEmptyValues:!0})}}const v=U();e.debug("[DynamoDB]: Default client configuration",{defaultClientConfig:v});const q=new R(v);function _(e){return function(e,t,s){const r=new a(t,{statusCode:e});return L(r.output.payload,s)}(400,"Something went wrong while validating your payload",e)}function P(e,t={}){let s=t.statusCode;e instanceof O&&(s=e.httpStatusCode);const r=a.boomify(e,{...t,statusCode:s}),o={...r.output.payload,message:e.message||r.output.payload.message};return L(o)}function $(e){const t=e;return void 0!==t.error}function L(e,t){return t?{...e,errors:t}:e}class O extends Error{constructor(e,t,s){super(e),this.name=s,this.httpStatusCode=t,this.constructor=O,Error.captureStackTrace(this,O),Object.setPrototypeOf(this,O.prototype)}}class F extends O{constructor(e){super(e,500,"DatabaseException")}}class H extends O{constructor(e){super(e,404,"NotFoundException")}}class B extends O{constructor(e){super(e,403,"ForbiddenException")}}class Q extends O{constructor(e){super(e,400,"BadRequestException")}}class G extends O{constructor(e){super(`Unreachable case: ${e}`,500,"UnreachableCaseError")}}class W{constructor(){this.errorServiceResponse=(e=>this.serviceResponseFromBoom(P(e))),this.serviceResponseFromBoom=(e=>({statusCode:e.statusCode,body:e})),this.serviceResponse=(e=>({statusCode:200,body:e})),this.tryExecute=(async e=>e().catch(e=>this.errorServiceResponse(e)))}}const V=864e5;class z{static getUnixTimestamp(e=new Date){return Math.round(+e/1e3)}static dateFromUnixTimestamp(e){return new Date(1e3*e)}static roundToMiliseconds(e=new Date){return this.dateFromUnixTimestamp(this.getUnixTimestamp(e))}static daysDateDiff(e,t){const s=Date.UTC(e.getFullYear(),e.getMonth(),e.getDate()),r=Date.UTC(t.getFullYear(),t.getMonth(),t.getDate());return Math.floor((r-s)/V)}static addMinutes(e,t=new Date){const s=new Date(t);return s.setMinutes(s.getMinutes()+e),s}static subtractMinutes(e,t=new Date){return this.addMinutes(-e,t)}static addDays(e,t=new Date){const s=new Date(t);return s.setDate(s.getDate()+e),s}}z.toDatetime=(e=>e.toISOString().slice(0,19).replace("T"," "));const{MONITORING_TABLE_NAME:Y="monitoring"}=process.env,j="schedulerGSI",X={TableName:Y,KeySchema:[{AttributeName:"hashKey",KeyType:"HASH"},{AttributeName:"sortKey",KeyType:"RANGE"}],AttributeDefinitions:[{AttributeName:"hashKey",AttributeType:"S"},{AttributeName:"sortKey",AttributeType:"S"},{AttributeName:"scheduler",AttributeType:"S"}],ProvisionedThroughput:{ReadCapacityUnits:1,WriteCapacityUnits:1},GlobalSecondaryIndexes:[{IndexName:j,KeySchema:[{AttributeName:"scheduler",KeyType:"HASH"},{AttributeName:"sortKey",KeyType:"RANGE"}],Projection:{ProjectionType:"ALL"},ProvisionedThroughput:{ReadCapacityUnits:1,WriteCapacityUnits:1}}]};class J{constructor(e=q){this.createTable=(()=>this.dynamoClient.createTable(X)),this.alertFromDynamoItem=(e=>{const{userId:t}=J.decodeHashKey(e.hashKey),{monitorId:s,alertId:r}=this.decodeAlertSortKey(e.sortKey);return{userId:t,id:r,monitorId:s,created:z.dateFromUnixTimestamp(e.created),type:e.type,scheduler:e.scheduler,action:e.action,actionValue:e.actionValue,operation:e.operation,value:e.value,windowDuration:e.windowDuration}}),this.monitorFromDynamoItem=(e=>{const{userId:t}=J.decodeHashKey(e.hashKey),{monitorId:s}=this.decodeMonitorSortKey(e.sortKey),r={userId:t,id:s,created:z.dateFromUnixTimestamp(e.created),name:e.name,scheduler:e.scheduler,apdex:e.apdex,type:e.type,headers:e.headers,body:e.body,endpoint:e.endpoint,isBookmarked:e.isBookmarked,location:e.location,regions:e.regions};return c(r,e=>void 0!==e)}),this.dynamoClient=new M(X.TableName,e),this.dynamoRawClient=e,this.tableName=X.TableName}static encodeHashKey({userId:e}){return`user#${e}`}static decodeHashKey(e){const t=e.split("#");return{userId:t[1]}}decodeAlertSortKey(e){const t=e.split("#");return{monitorId:t[1],alertId:t[2]}}decodeMonitorSortKey(e){const t=e.split("#");return{monitorId:t[1]}}}const Z={gettingStarted:{completedTasks:{}}},ee="userInfo",te=(e,t,s)=>({Key:{hashKey:J.encodeHashKey({userId:e}),sortKey:ee},TableName:X.TableName,UpdateExpression:`set gettingStarted.completedTasks.${s} = :created`,ConditionExpression:`attribute_not_exists(gettingStarted.completedTasks.${s})`,ExpressionAttributeValues:{":created":z.getUnixTimestamp(t)}});class se{constructor(t=q){this.createTable=(()=>this.dynamoClient.createTable(X)),this.getUserInfo=(e=>{const t=J.encodeHashKey({userId:e});return this.dynamoClient.queryTable({KeyConditionExpression:"hashKey = :hashKey and sortKey = :sortKey",ExpressionAttributeValues:{":hashKey":t,":sortKey":ee}}).then(t=>{if(!t.Items)throw new H(`Could not find userInfo for user: ${e}`);return t.Items.length>0?this.userInfoFromDynamoRow(t.Items[0]):this.insertUserInfoRow({hashKey:J.encodeHashKey({userId:e}),sortKey:ee,...Z})})}),this.completeGettingStartedTask=(t=>{const{taskName:s,completedOn:r,userId:o}=t;return this.dynamoClient.updateItem({...te(o,r,s),ReturnValues:"UPDATED_NEW"}).then(e=>{if(!e.Attributes)throw new Error(`Could not update completeGettingStartedTask, userId: ${o}`);const t=e.Attributes;return this.userInfoFromDynamoRow({hashKey:J.encodeHashKey({userId:o}),sortKey:ee,...t})}).catch(r=>{if(e.error("Could not update completeGettingStartedTask",r,{params:t}),"ConditionalCheckFailedException"===r.name)throw new Q(`Task [${s}] is already completed`);if("ValidationException"===r.name&&"The document path provided in the update expression is invalid for update"===r.message)throw new H("Getting started task cannot be completed before userInfo is created");throw r})}),this.insertUserInfoRow=(e=>this.dynamoClient.putDocumentToDynamo({Item:e}).then(t=>this.userInfoFromDynamoRow(e))),this.userInfoFromDynamoRow=(e=>{const{userId:t}=J.decodeHashKey(e.hashKey),s=u(e.gettingStarted.completedTasks,z.dateFromUnixTimestamp);return{userId:t,gettingStarted:{...e.gettingStarted,completedTasks:s}}}),this.dynamoClient=new M(X.TableName,t)}}const re=new se,oe=(e,t)=>`${e} object expected but got: \`${t.originalValue}\``;function ne(e,t,s={abortEarly:!1,strict:!0}){if(void 0===t||"function"==typeof t){const e={non_field_errors:`Object expected but got: \`${t}\``};return Promise.resolve({error:!0,errors:e})}return e.validate(t,s).then(e=>({error:!1})).catch(e=>{return{error:!0,errors:e.inner.reduce((e,t)=>{let s=t.path;return void 0===s&&(s="non_field_errors"),e[s]=t.message,e},{})}})}const ie=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;class ae{}ae.isUUIDv4=(e=>ie.test(e));const ce=(e="id")=>l().test("isUUIDv4","uuid/v4 expected but got: `${value}`",e=>ae.isUUIDv4(e)).required(`${e} is required`),ue=ce(),he=ce("userId"),de=y().shape({userId:he,completedOn:p().required("created is required"),taskName:l().required("taskName is required")});class me extends W{constructor(e=q){super(),this.getUserInfo=(e=>this.tryExecute(async()=>{const t=await this.datasource.getUserInfo(e);return this.serviceResponse(t)})),this.completeGettingStartedTask=(async(e,t)=>{const s={userId:e,taskName:t,completedOn:new Date},r=await function(e){return ne(de,e)}(s);return r.error?this.serviceResponseFromBoom(_(r.errors)):this.tryExecute(async()=>{const e=await this.datasource.completeGettingStartedTask(s);return this.serviceResponse(e)})}),this.datasource=new se(e)}}const le=new me;class ye extends W{constructor(){super(...arguments),this.cnameLookup=(e=>{const t=T(e),s=(t.protocol?t.hostname:t.href)||"";return this.tryExecute(()=>new Promise((e,t)=>{b(s,(s,r)=>{s?t(new Error(s.message)):e(this.serviceResponse(r))})}))})}}class pe{constructor(e){this.canAccessEntity=(e=>e.userId===this.userId),this.cannotAccessEntity=(e=>!this.canAccessEntity(e)),this.userId=e}}pe.of=(e=>new pe(e));const{STATUS_PAGE_TABLE_NAME:ge="status-page"}=process.env,we="searchGSI",Ie="customDomainGSI",be={TableName:ge,AttributeDefinitions:[{AttributeName:"id",AttributeType:"S"},{AttributeName:"userId",AttributeType:"S"},{AttributeName:"customDomain",AttributeType:"S"},{AttributeName:"created",AttributeType:"N"}],KeySchema:[{AttributeName:"id",KeyType:"HASH"}],ProvisionedThroughput:{ReadCapacityUnits:1,WriteCapacityUnits:1},GlobalSecondaryIndexes:[{IndexName:we,KeySchema:[{AttributeName:"userId",KeyType:"HASH"},{AttributeName:"created",KeyType:"RANGE"}],Projection:{ProjectionType:"ALL"},ProvisionedThroughput:{ReadCapacityUnits:1,WriteCapacityUnits:1}},{IndexName:Ie,KeySchema:[{AttributeName:"customDomain",KeyType:"HASH"},{AttributeName:"created",KeyType:"RANGE"}],Projection:{ProjectionType:"ALL"},ProvisionedThroughput:{ReadCapacityUnits:1,WriteCapacityUnits:1}}]};class Te{constructor(e=q){this.createTable=(()=>this.dynamoClient.createTable(be)),this.updateStatusPage=(e=>this.putStatusPage(e)),this.createStatusPage=(e=>{const t={id:f.v4(),created:new Date,...e};return this.putStatusPage(t)}),this.putStatusPage=(e=>{const t=this.statusPageToDynamoItem(e);return this.dynamoClient.putDocumentToDynamo({Item:t}).then(t=>e)}),this.findStatusPages=(e=>{const t=this.encodeUserId(e);return this.dynamoClient.queryTable({IndexName:we,KeyConditionExpression:"userId = :userId",ExpressionAttributeValues:{":userId":t}}).then(e=>{const t=e.Items;return t.map(this.statusPageFromDynamoItem)})}),this.deleteStatusPage=(e=>this.dynamoClient.deleteItem({Key:{id:this.encodeId(e)}})),this.lookupStatusPage=(e=>this.dynamoClient.queryTable({IndexName:Ie,KeyConditionExpression:"customDomain = :customDomain",ExpressionAttributeValues:{":customDomain":e}}).then(t=>{if(!t.Items||0===t.Items.length)throw new H(`Could not find statusPage: customDomain = ${e}`);const s=t.Items[0];return this.statusPageFromDynamoItem(s)})),this.getStatusPage=(e=>this.dynamoClient.queryTable({KeyConditionExpression:"id = :id",ExpressionAttributeValues:{":id":this.encodeId(e)}}).then(t=>{if(!t.Items||0===t.Items.length)throw new H(`Could not find statusPage: id = ${e}`);const s=t.Items[0];return this.statusPageFromDynamoItem(s)})),this.statusPageToDynamoItem=(e=>{const t=this.encodeUserId(e.userId),s=this.encodeId(e.id);return{userId:t,id:s,monitors:e.monitors,created:z.getUnixTimestamp(e.created),includeAllMonitors:e.includeAllMonitors,niceName:e.niceName,logo:e.logo,customDomain:e.customDomain,customCss:e.customCss,customHtmlFooter:e.customHtmlFooter,customHtmlHeader:e.customHtmlHeader}}),this.statusPageFromDynamoItem=(e=>{const t=this.decodeHashKey(e.userId),s=this.decodeHashKey(e.id);return{id:s,userId:t,monitors:e.monitors,created:z.dateFromUnixTimestamp(e.created),includeAllMonitors:e.includeAllMonitors,niceName:e.niceName,logo:e.logo,customDomain:e.customDomain,customCss:e.customCss,customHtmlFooter:e.customHtmlFooter,customHtmlHeader:e.customHtmlHeader}}),this.dynamoClient=new M(be.TableName,e)}encodeId(e){return`id#${e}`}encodeUserId(e){return`userId#${e}`}decodeHashKey(e){const t=e.split("#");return t[1]}}const fe=new Te,Ce={userId:he,niceName:l().required("niceName is required"),includeAllMonitors:g().required("includeAllMonitors is required"),monitors:w().of(l()),customDomain:l(),logo:l()},Ee=y().shape(Ce),De={...Ce,id:ue},Ae=y().shape(De);class Se extends W{constructor(e=q){super(),this.getStatusPage=(async e=>this.tryExecute(async()=>{const t=await this.datasource.getStatusPage(e);return this.serviceResponse(t)})),this.lookupStatusPage=(async e=>this.tryExecute(async()=>{const t=await this.datasource.lookupStatusPage(e);return this.serviceResponse(t)})),this.updateStatusPage=(async(e,t)=>{const s=await function(e){return ne(Ae,e)}(t);return s.error?this.serviceResponseFromBoom(_(s.errors)):this.tryExecute(async()=>{const s=await this.datasource.getStatusPage(t.id);if(pe.of(e).cannotAccessEntity(s))throw new B("You are not authorized to perform this action");const r={...t,created:s.created},o=await this.datasource.updateStatusPage(r);return this.serviceResponse(o)})}),this.deleteStatusPage=(async(e,t)=>this.tryExecute(async()=>{const s=await this.datasource.getStatusPage(t);if(pe.of(e).cannotAccessEntity(s))throw new B("You are not authorized to perform this action");return await this.datasource.deleteStatusPage(s.id),this.serviceResponse(s)})),this.createStatusPage=(async(e,t)=>{const s=await function(e){return ne(Ee,e)}(t);return s.error?this.serviceResponseFromBoom(_(s.errors)):this.tryExecute(async()=>{if(pe.of(e).cannotAccessEntity(t))throw new B("You are not authorized to perform this action");const s=await this.datasource.createStatusPage(t);return{statusCode:201,body:s}})}),this.findStatusPages=(async e=>this.tryExecute(async()=>{const t=await this.datasource.findStatusPages(e);return{statusCode:200,body:t}})),this.datasource=new Te(e)}}const Ke=new Se;class Ne extends J{constructor(e=q){super(e),this.bookmarkMonitor=((e,t,s)=>{const r=J.encodeHashKey({userId:e}),o=this.encodeSortKey({monitorId:t});return this.dynamoClient.updateItem({Key:{hashKey:r,sortKey:o},UpdateExpression:"set isBookmarked = :isBookmarked",ExpressionAttributeValues:{":isBookmarked":s},ReturnValues:"ALL_NEW"}).then(e=>this.monitorFromDynamoItem(e.Attributes))}),this.getMonitor=((e,t)=>{const s=J.encodeHashKey({userId:e}),r=this.encodeSortKey({monitorId:t});return this.dynamoClient.queryTable({KeyConditionExpression:"hashKey = :hashKey and sortKey = :sortKey",ExpressionAttributeValues:{":hashKey":s,":sortKey":r}}).then(e=>{if(!e.Items||0===e.Items.length)throw new H(`Could not find monitor id = ${t}`);return this.monitorFromDynamoItem(e.Items[0])})}),this.deleteMonitor=((e,t)=>{const s=J.encodeHashKey({userId:e}),r=this.encodeSortKey({monitorId:t});return this.dynamoClient.deleteItem({Key:{hashKey:s,sortKey:r}})}),this.searchMonitors=(e=>this.dynamoClient.queryTable({KeyConditionExpression:"hashKey = :hashKey and begins_with(sortKey, :sortKey)",ExpressionAttributeValues:{":hashKey":J.encodeHashKey({userId:e}),":sortKey":"monitor"}}).then(this.mapDynamoRows)),this.getMonitorsByScheduler=(e=>this.dynamoClient.queryTable({KeyConditionExpression:"scheduler = :scheduler and begins_with(sortKey, :sortKey)",IndexName:j,ExpressionAttributeValues:{":scheduler":e,":sortKey":"monitor"}}).then(this.mapDynamoRows)),this.createMonitor=(async e=>{const t={...e,id:f.v4(),created:new Date},s=this.monitorToDynamoItem(t),r=[{Put:{Item:s,TableName:this.tableName}}],o=await this.userDatasource.getUserInfo(t.userId);if(!o.gettingStarted.completedTasks.createMonitor){const e=te(t.userId,t.created,"createMonitor");r.push({Update:e})}return this.dynamoRawClient.transactWriteItems({TransactItems:r}).then(e=>t)}),this.updateMonitor=(e=>this._putMonitor(e)),this._putMonitor=(e=>{const t=this.monitorToDynamoItem(e);return this.dynamoClient.putDocumentToDynamo({Item:t}).then(t=>e)}),this.monitorToDynamoItem=(e=>({hashKey:Ne.encodeHashKey(e),sortKey:this.encodeSortKey({monitorId:e.id}),name:e.name,body:h(e,"body"),scheduler:e.scheduler,apdex:h(e,"apdex"),endpoint:e.endpoint,headers:h(e,"headers"),isBookmarked:e.isBookmarked,location:e.location,regions:h(e,"regions"),type:e.type,created:z.getUnixTimestamp(e.created)})),this.mapDynamoRows=(e=>{const t=e.Items;return t.map(this.monitorFromDynamoItem)}),this.userDatasource=new se(e)}encodeSortKey({monitorId:e}){return`monitor#${e}`}}const xe=new Ne,ke={endpoint:l().required("Endpoint is required so we know where to send our requests"),type:l().oneOf(["latency-check","https-check","certificate-check"]).required("Monitor type is required")},Ue=y().shape(ke).typeError(e=>oe("Executor",e));const Me=e=>oe("Monitor",e),Re={...ke,userId:he,isBookmarked:g().required("isBookmarked is required"),scheduler:l().required("Scheduler is required, so we know how often to monitor your endpoint"),name:l().required("Name is required, so you can find your monitors easily")},ve={id:ue},qe=y().shape(Re).typeError(Me),_e=y().shape({...Re,...ve}).typeError(Me),Pe={...Re,body:l(),headers:y(),apdex:I().required("Apdex value is required so we can better measure performance of your endpoint"),regions:w().of(l()).required("Regions are required so we know from where to monitor your endpoint").test("isNonEmpty","At least one region should be selected but got: `${value}`",e=>(e||[]).length>0)},$e=y().shape(Pe).typeError(Me),Le=y().shape({...Pe,...ve}).typeError(Me);function Oe(e){return"latency-check"===e.type}class Fe extends W{constructor(e=q){super(),this.bookmarkMonitor=(async(e,t)=>this.tryExecute(async()=>{let s=await this.datasource.getMonitor(e,t);return s=await this.datasource.bookmarkMonitor(s.userId,s.id,!s.isBookmarked),this.serviceResponse(s)})),this.getMonitor=(async(e,t)=>this.tryExecute(async()=>{const s=await this.datasource.getMonitor(e,t);if(pe.of(e).cannotAccessEntity(s))throw new B("You are not authorized to perform this action");return this.serviceResponse(s)})),this.deleteMonitor=(async(e,t)=>this.tryExecute(async()=>{const s=await this.datasource.getMonitor(e,t);return await this.datasource.deleteMonitor(s.userId,s.id),this.serviceResponse(s)})),this.getMonitorsByScheduler=(async e=>this.tryExecute(async()=>{const t=await this.datasource.getMonitorsByScheduler(e);return this.serviceResponse(t)})),this.getMonitors=(async e=>this.tryExecute(async()=>{const t=await this.datasource.searchMonitors(e);return this.serviceResponse(t)})),this.updateMonitor=(async(e,t)=>{const s=await function(e){return Oe(e)?ne(Le,e):ne(_e,e)}(t);return s.error?this.serviceResponseFromBoom(_(s.errors)):this.tryExecute(async()=>{if(pe.of(e).cannotAccessEntity(t))throw new B("You are not authorized to perform this action");const s=await this.datasource.getMonitor(e,t.id);if(s.type!==t.type)throw new Q("Monitor type cannot be changes. Please create a new monitor.");const r=await this.datasource.updateMonitor({...t,created:s.created});return{statusCode:200,body:r}})}),this.createMonitor=(async(e,t)=>{const s=await function(e){return Oe(e)?ne($e,e):ne(qe,e)}(t);return s.error?this.serviceResponseFromBoom(_(s.errors)):this.tryExecute(async()=>{if(pe.of(e).cannotAccessEntity(t))throw new B("You are not authorized to perform this action");const s=await this.datasource.createMonitor(t);return{statusCode:201,body:s}})}),this.datasource=new Ne(e)}}const He=new Fe;class Be extends J{constructor(e=q){super(e),this.createAlert=(async e=>{const t={...e,id:f.v4(),created:new Date},s=this.alertToDynamoItem(t),r=[{Put:{Item:s,TableName:this.tableName}}],o=await this.userDatasource.getUserInfo(t.userId);if(!o.gettingStarted.completedTasks.createAlert){const e=te(t.userId,t.created,"createAlert");r.push({Update:e})}return this.dynamoRawClient.transactWriteItems({TransactItems:r}).then(e=>t)}),this.deleteAlert=((e,t,s)=>{const r=J.encodeHashKey({userId:e}),o=this.encodeSortKey({monitorId:t,alertId:s});return this.dynamoClient.deleteItem({Key:{hashKey:r,sortKey:o}})}),this.getAlert=((e,t,s)=>{const r=J.encodeHashKey({userId:e}),o=this.encodeSortKey({monitorId:t,alertId:s});return this.dynamoClient.queryTable({KeyConditionExpression:"hashKey = :hashKey and sortKey = :sortKey",ExpressionAttributeValues:{":hashKey":r,":sortKey":o}}).then(e=>{if(!e.Items||0===e.Items.length)throw new H(`Could not find alert id = ${s}`);return this.alertFromDynamoItem(e.Items[0])})}),this.listAlerts=(e=>{return this.searchAlerts(e,"alert")}),this.listAlertsForMonitor=((e,t)=>{const s=`alert#${t}`;return this.searchAlerts(e,s)}),this.searchAlerts=((e,t)=>this.dynamoClient.queryTable({KeyConditionExpression:"hashKey = :hashKey and begins_with(sortKey, :sortKey)",ExpressionAttributeValues:{":hashKey":J.encodeHashKey({userId:e}),":sortKey":t}}).then(this.mapDynamoRows)),this.updateAlert=(e=>this._putAlert(e)),this._putAlert=(e=>{const t=this.alertToDynamoItem(e);return this.dynamoClient.putDocumentToDynamo({Item:t}).then(t=>e)}),this.alertToDynamoItem=(e=>({hashKey:J.encodeHashKey(e),sortKey:this.encodeSortKey({alertId:e.id,monitorId:e.monitorId}),scheduler:e.scheduler,created:z.getUnixTimestamp(e.created),type:e.type,action:e.action,actionValue:e.actionValue,operation:e.operation,value:e.value,windowDuration:e.windowDuration})),this.mapDynamoRows=(e=>{const t=e.Items;return t.map(this.alertFromDynamoItem)}),this.userDatasource=new se(e)}encodeSortKey({monitorId:e,alertId:t}){return`alert#${e}#${t}`}}const Qe=new Be,Ge={userId:he,monitorId:l().required("monitorId is required"),actionValue:l().required("actionValue is required"),value:I().required("value is required"),windowDuration:I().required("windowDuration is required"),type:l().oneOf(["downtime"]).required("Alert type is required"),operation:l().oneOf(["gt","lt"]).required("Alert operation is required"),action:l().oneOf(["email","webhook"]).required("Alert action is required")},We=y().shape(Ge).typeError(e=>oe("Alert",e));function Ve(e){return ne(We,e)}const ze="alert-any-monitor";class Ye extends W{constructor(e=q){super(),this.listAlerts=(async e=>this.tryExecute(async()=>{const t=await this.alertDatasource.listAlerts(e);return this.serviceResponse(t)})),this.deleteAlert=(async(e,t)=>this.tryExecute(async()=>{const s=await this.getAlertById(e,t);return await this.alertDatasource.deleteAlert(e,s.monitorId,t),this.serviceResponse(s)})),this.updateAlert=(async(e,t,s)=>{const r=await Ve(s);return r.error?this.serviceResponseFromBoom(_(r.errors)):this.tryExecute(async()=>{const r=await this.getAlertById(e,t);if(pe.of(e).cannotAccessEntity(r))throw new B("You are not authorized to perform this action");const o=await this.getSchedulerByMonitorIdentifier(e,s.monitorId),n=await this.alertDatasource.updateAlert({...s,scheduler:o,id:t,created:r.created});return{statusCode:200,body:n}})}),this.createAlert=(async(e,t)=>{const s=await Ve(t);return s.error?this.serviceResponseFromBoom(_(s.errors)):this.tryExecute(async()=>{if(pe.of(e).cannotAccessEntity(t))throw new B("You are not authorized to perform this action");const s=await this.getSchedulerByMonitorIdentifier(e,t.monitorId),r=await this.alertDatasource.createAlert({...t,scheduler:s});return{statusCode:201,body:r}})}),this.getAlertById=(async(e,t)=>{const s=await this.alertDatasource.listAlerts(e),r=s.find(e=>e.id===t);if(!r)throw new H(`Could not find alert id = ${t}`);return r}),this.getSchedulerByMonitorIdentifier=(async(e,t)=>{let s=ze;if(t!==ze){const r=await this.monitorDatasource.getMonitor(e,t);s=r.scheduler}return s}),this.alertDatasource=new Be(e),this.monitorDatasource=new Ne(e)}}const je=new Ye;class Xe extends J{constructor(e=q){super(e),this.isMonitor=(e=>e.sortKey.startsWith("monitor")),this.getWorkload=(e=>{const t=this.dynamoClient.queryTable({KeyConditionExpression:"scheduler = :scheduler",IndexName:j,ExpressionAttributeValues:{":scheduler":e}}),s=this.dynamoClient.queryTable({KeyConditionExpression:"scheduler = :scheduler",IndexName:j,ExpressionAttributeValues:{":scheduler":ze}});return Promise.all([t,s]).then(([e,t])=>{const s=t.Items,r={[ze]:s.map(this.alertFromDynamoItem)},o=e.Items,n=[];for(let e of o)if(this.isMonitor(e))n.push(this.monitorFromDynamoItem(e));else{const t=this.alertFromDynamoItem(e),s=r[t.monitorId]||[];s.push(t),r[t.monitorId]=s}return{monitors:n,monitorAlerts:r}})})}}const Je=new Xe;const Ze="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",et={port:80,path:"/",method:"GET",headers:{"User-Agent":Ze}};class tt{static hasRedirect(e){return tt.isRedirectableStatus(e.statusCode)}static isRedirectableStatus(e){return void 0!==e&&(e>=300&&e<400)}}tt.timeoutableGet=((e,t)=>(function(e,t){return new Promise((s,r)=>{const o=setTimeout(()=>{r(`Action timed out using timeout of ${t}ms.`)},t);e().then(e=>{clearTimeout(o),s(e)}).catch(e=>{clearTimeout(o),r(e)})})})(()=>tt.get(e),t)),tt.hasHttpsRedirect=(e=>{if(tt.hasRedirect(e)){const t=e.headers.location;if(t&&"https:"===T(t).protocol)return!0}return!1}),tt.get=(e=>(tt.isRequestOptionsAsObject(e)&&(e={...et,...e}),new Promise((t,s)=>{o(e,e=>t(e)).on("error",e=>{s(e)})}))),tt.isRequestOptionsAsObject=(e=>!d(e));const st={port:443,path:"/",method:"GET",headers:{"User-Agent":Ze}},rt=parseInt(process.env.EXECUTOR_TIMEOUT||"10000",10);class ot extends W{constructor(e=C){super(),this.executeMonitorCheck=(async e=>{const t=await function(e){return ne(Ue,e)}(e);return t.error?this.serviceResponseFromBoom(_(t.errors)):this.tryExecute(async()=>{const{endpoint:t,type:s,...r}=e,o=T(t);switch(s){case"latency-check":const e=await this.latencyCheck({url:o,...r});return this.serviceResponse(e);case"certificate-check":const t=await this.certificateCheck(o);return this.serviceResponse(t);case"https-check":const n=await this.httpsCheck(o);return this.serviceResponse(n);default:throw new G(s)}})}),this.fetch=e,this.certificateCache={}}latencyCheck({url:e,timeout:t=ot.DEFAULT_EXECUTOR_TIMEOUT,headers:s}){return new Promise(async r=>{const{href:o}=e;if(!o)return r({httpStatus:400,error:`Expected url, but got \`${o}\``});this.fetch(o,{timeout:t,headers:s}).then(e=>e.blob().then(t=>{const s=e.timings,o=t.size;r({blobSize:o,timings:s,httpStatus:e.status})})).catch(e=>{let s=e.message,o=400;e.message.includes("network timeout")&&(s=`Latency check timed out using timeout of ${t}ms.`,o=408),r({error:s,httpStatus:o})})})}async certificateCheck(e,t=ot.DEFAULT_EXECUTOR_TIMEOUT){const s=e.protocol?e.hostname:e.href;return s?new Promise(e=>{const r=setTimeout(()=>{e({hasCertificate:!1,error:`Certificate check timed out using timeout of ${t}ms.`})},t),o={...st,hostname:s},i=n.get(o).on("error",t=>{clearTimeout(r);const s=t.message;e({hasCertificate:!1,error:s})});i.on("socket",t=>{t.on("secureConnect",()=>{let o=t.getPeerCertificate();m(o)?o=this.certificateCache[s]:this.certificateCache[s]=o;const n=this.parseCertificate(o);e(n),clearTimeout(r)})})}):{hasCertificate:!1,error:`Expected hostname, but got \`${s}\``}}async httpsCheck(e,t=ot.DEFAULT_EXECUTOR_TIMEOUT){const s=e.protocol?e.hostname:e.href;return s?tt.timeoutableGet({hostname:s},t).then(e=>{const t=tt.hasHttpsRedirect(e);return t?{secure:t}:{secure:t,error:`Could not find https redirect from ${s}, statusMessage: ${e.statusMessage}`}}).catch(e=>{const t={secure:!1,error:e.message};return t}):{secure:!1,error:`Expected hostname, but got \`${s}\``}}parseCertificate(e){const t=new Date(e.valid_to),s=z.daysDateDiff(new Date,t),r=void 0!==s&&s>0;return r?{hasCertificate:r,valid_to:t,expires_in:s}:{hasCertificate:r,error:"Could not find PeerCertificate."}}}ot.DEFAULT_EXECUTOR_TIMEOUT=rt;const nt=new ot,it={TableName:"connection",AttributeDefinitions:[{AttributeName:"monitorId",AttributeType:"S"},{AttributeName:"connectionId",AttributeType:"S"}],KeySchema:[{AttributeName:"monitorId",KeyType:"HASH"},{AttributeName:"connectionId",KeyType:"RANGE"}],ProvisionedThroughput:{ReadCapacityUnits:1,WriteCapacityUnits:1}};class at{constructor(e=q){this.createTable=(()=>this.dynamoClient.createTable(it)),this.createConnection=(e=>{const t=f.v4(),s={connectionId:t,monitorId:e};return this.dynamoClient.putDocumentToDynamo({Item:s}).then(e=>t)}),this.deleteConnection=((e,t)=>{const s={monitorId:e,connectionId:t};return this.dynamoClient.deleteItem({Key:s})}),this.findConnectionsForMonitor=(e=>this.dynamoClient.queryTable({KeyConditionExpression:"monitorId = :monitorId",ExpressionAttributeValues:{":monitorId":e}}).then(e=>e.Items)),this.dynamoClient=new M(it.TableName,e)}}const ct=new at;class ut{}ut.startTimer=(()=>D.now());const ht={user:process.env.POSTGRES_USER||"postgres",password:process.env.POSTGRES_PASSWORD||"postgres",port:parseInt(process.env.POSTGRES_PORT||"5431",10),database:process.env.POSTGRES_DATABASE_NAME||"postgres",host:process.env.POSTGRES_HOST||"localhost"};class dt{constructor(t){this.isConfig=(e=>void 0!==e.text),this.close=(()=>this.pool.end()),this.query=(t=>{ut.startTimer();return this.pool.query(t).then(e=>e).catch(t=>{const s="[Postgres]: Something went wrong while executing query";throw e.error(s,t),new F(`${s}: ${t.message}`)})}),this.pool=t}}const mt=new dt(new E(ht)),lt="checks";class yt{constructor(e=mt){this.getMonitorChecks=(e=>{const t=this.getMonitorChecksQueryConfig(e);return this.client.query(t).then(e=>{const t=e.rows;return t})}),this.getMonitorChecksQueryConfig=(({monitorId:e,pagination:t={},timeFilter:s,tableName:r})=>{let o=`SELECT * FROM ${lt}.${r} WHERE ${lt}.${r}.monitor_id = $1 `;if(s){const{from:e,to:t}=s;o=`${o} AND ${lt}.${r}.time BETWEEN '${z.toDatetime(e)}'::timestamp AND ${t?`'${z.toDatetime(t)}'`:"now()"}::timestamp `}o=`${o} ORDER BY time DESC LIMIT $2 OFFSET $3`;const{limit:n,offset:i=0}=t;return{text:o,values:[e,n,i]}}),this.client=e}}const pt="latency",gt=`CREATE TABLE ${lt}.${pt} (\n time TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n user_id UUID NOT NULL,\n monitor_id UUID NOT NULL,\n region TEXT NOT NULL,\n status SMALLINT NOT NULL,\n error TEXT NULL,\n total_time REAL NULL,\n tls_handshake_time REAL NULL,\n tcp_connection_time REAL NULL,\n dns_lookup_time REAL NULL,\n first_byte_time REAL NULL,\n blob_size REAL NULL\n);`,wt=`SELECT create_hypertable('${lt}.${pt}', 'time');`;class It extends yt{constructor(e=mt){super(e),this.createTable=(()=>this.client.query(gt).then(e=>this.client.query(wt))),this.getLatencyChecks=(e=>this.getMonitorChecks({...e,tableName:pt})),this.insertCheckData=(e=>this.insertLatencyCheck(e)),this.insertLatencyCheck=(e=>{const t=this.getInsertQueryConfig(e);return this.client.query(t).then(e=>{const t=e.rows[0];return t})})}getInsertQueryConfig({userId:e,monitorId:t,region:s,data:r,created:o}){const n=h(r,"timings",{}),i=r.blobSize,a=r.error;return{name:"insert-latency-check",text:`INSERT INTO ${lt}.${pt} `+"(time, user_id, monitor_id, region, status, error, total_time, tls_handshake_time, tcp_connection_time, dns_lookup_time, first_byte_time, blob_size) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *",values:[o,e,t,s,r.httpStatus,a,n.totalTime,n.tlsHandshakeTime,n.tcpConnectionTime,n.dnsLookupTime,n.firstByteTime,i]}}}new It;class bt{constructor(e=mt){this.createChecksSchema=(()=>this.client.query({name:"create-schema",text:`CREATE SCHEMA ${lt};`})),this.insertCheckData=((e,t)=>{switch(t){case"latency-check":const s=e;return this.latency.insertCheckData(s);case"certificate-check":case"https-check":throw new Error("Not implemented");default:throw new G(t)}}),this.getLatency=(()=>this.latency),this.latency=new It(e),this.client=e}}const Tt=new bt;class ft extends W{constructor(e=mt,t=q){super(),this.getChecks=((e,t,s={limit:20})=>this.tryExecute(async()=>{const r=await this.monitorDatasource.getMonitor(e,t);if(pe.of(e).cannotAccessEntity(r))throw new B("You are not authorized to perform this action");if(Oe(r))return this.latencyCheckDatasource.getLatencyChecks({monitorId:t,pagination:s}).then(this.serviceResponse);throw new Error("Not implemented")}));const s=new bt(e);this.latencyCheckDatasource=s.getLatency(),this.monitorDatasource=new Ne(t)}}const Ct=new ft;class Et{constructor(t){this.getQueueURL=(e=>this.sqs.getQueueUrl({QueueName:e}).promise()),this.sendMessage=(t=>(e.debug("[SQS]: sending message",{params:t}),this.sqs.sendMessage(t).promise().catch(s=>{throw e.error("[SQS]: failed to send message",s,{params:t}),s}))),this.sendMessageBatch=(t=>(e.debug("[SQS]: sending message batch",{params:t}),this.sqs.sendMessageBatch(t).promise().catch(s=>{throw e.error("[SQS]: failed to send message batch",s,{params:t}),s}))),this.receiveMessage=(t=>(e.debug("[SQS]: receiving message",{params:t}),this.sqs.receiveMessage(t).promise().catch(s=>{throw e.error("[SQS]: failed to receive message",s,{params:t}),s}))),this.createQueue=(t=>(e.info("[SQS]: creating queue",{params:t}),this.sqs.createQueue(t).promise().catch(s=>{throw e.error("[SQS]: failed to create queue",s,{params:t}),s}))),this.sqs=t}}const Dt=function(e={}){const{AWS_SQS_ENDPOINT:t}=process.env;let s=e.endpoint||t;return N||s||(s="http://localhost:9324"),k({...e,endpoint:s})}();e.debug("[SQS]: Default client configuration",{defaultSqsConfiguration:Dt});const At=new s(Dt);class St{constructor(t,s=At){this.getQueueUrl=(()=>this.QueueUrl?Promise.resolve(this.QueueUrl):this.sqsClient.getQueueURL(this.QueueName).then(({QueueUrl:t})=>{if(!t)throw new Error(`[SQS]: Could not getQueueUrl for ${this.QueueName}`);return e.debug(`[SQS]: queue ${t} warmed up`),this.QueueUrl=t,this.QueueUrl})),this.createQueue=(()=>this.sqsClient.createQueue({QueueName:this.QueueName})),this.sendMessage=(e=>this.queueParams(e).then(this.sqsClient.sendMessage)),this.sendMessageBatch=(e=>this.queueParams(e).then(this.sqsClient.sendMessageBatch)),this.receiveMessage=(e=>this.queueParams(e).then(this.sqsClient.receiveMessage)),this.QueueName=t,this.sqsClient=new Et(s)}async queueParams(e){const t=await this.getQueueUrl();return{...e,QueueUrl:t}}}class Kt{constructor(e=At){this.QUEUE_NAME="alert-notification",this.process=(e=>{const t=JSON.stringify(e);return this.queue.sendMessage({MessageBody:t,MessageAttributes:{Timestamp:{DataType:"String",StringValue:(new Date).toISOString()}}})}),this.queue=new St(this.QUEUE_NAME,e)}}class Nt{constructor(e){this.notify=((e,t)=>{const s=JSON.stringify({...e,timestamp:t});return this.emailDispatcher.send({text:s,html:s,recipient:"test@gmail.com",subject:"Monitor alert violation",source:"alert@meshwatch.dev"})}),this.emailDispatcher=e}}async function xt(e=q,t=mt){await async function(e){const t=[X,it,be];return await Promise.all(t.map(e.createTable))}(e),await async function(e){const t=new bt(e);await t.createChecksSchema(),await t.getLatency().createTable()}(t)}export{Ye as AlertService,Kt as AlertViolationNotifier,ye as DnsService,Be as DynamoAlertDatasource,at as DynamoConnectionDatasource,R as DynamoDBClient,Xe as DynamoExecutorsWorkloadDatasource,Ne as DynamoMonitorDatasource,Te as DynamoStatusPageDatasource,se as DynamoUserDatasource,Nt as EmailNotificationService,bt as MonitorChecksDatasource,ft as MonitorChecksService,ot as MonitorExecutorService,Fe as MonitorsService,dt as PostgresClient,St as SQSQueue,Se as StatusPageService,me as UserService,je as alertService,P as boomify,Qe as dynamoAlertDatasource,ct as dynamoConnectionDatasource,Je as dynamoExecutorsWorkloadDatasource,xe as dynamoMonitorDatasource,fe as dynamoStatusPageDatasource,re as dynamoUserDatasource,xt as initializeTables,$ as isBoom,Oe as isLatencyCheckMonitor,Tt as monitorChecksDatasource,Ct as monitorChecksService,nt as monitorExecutorService,He as monitorsService,Ke as statusPageService,yt as timescaleMonitorCheckDatasource,le as userService}; //# sourceMappingURL=meshwatchbackend-core.es.production.js.map