@meshwatch/backend-core
Version:
Meshwatch backend core services.
3 lines (2 loc) • 38.5 kB
JavaScript
"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}var t=require("@meshwatch/logging"),s=require("aws-sdk/clients/all"),r=require("http"),o=require("https"),n=e(o),i=e(require("@hapi/boom")),a=require("lodash"),c=require("yup"),u=require("dns"),h=require("url"),d=e(require("uuid")),m=e(require("@meshwatch/node-fetch")),l=require("pg"),y=require("perf_hooks");const p=!(!process.env.LAMBDA_TASK_ROOT&&!process.env.AWS_EXECUTION_ENV),g="true"===(process.env.IS_LOCAL||"false"),b="true"===(process.env.IS_OFFLINE||"false"),w=p&&!g&&!b,I=/^https:\/\/dynamodb\.((us(-gov)?|ap|ca|cn|eu|sa)-(central|(north|south)?(east|west)?)-\d).amazonaws\.com$/;function T(e){const{AWS_REGION:t,AWS_ACCESS_KEY_ID:s,AWS_SECRET_ACCESS_KEY:n}=process.env;let i=t,a=void 0,c=void 0;w||(i=i||"us-east-1",a=s||"accessKeyId",c=n||"secretAccessKey");const u={keepAlive:!0,maxSockets:50},h=(d=e.endpoint,!d||I.test(d)?new o.Agent({...u,rejectUnauthorized:!0}):new r.Agent(u));var d;return{region:i,accessKeyId:a,secretAccessKey:c,httpOptions:{agent:h},...e}}function f(e={}){const{AWS_DYNAMODB_ENDPOINT:t}=process.env;let s=e.endpoint||t;return w||s||(s="http://localhost:8000"),T({...e,endpoint:s})}class D{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 S{constructor(e){this.createTable=(e=>this.dynamodb.createTable(e).promise().then(e=>e).catch(s=>{throw t.logger.error("[DynamoDB]: Unable to create table",s,{createTableParams:e}),s})),this.transactWriteItems=(e=>this.dynamodbDocumentClient.transactWrite(e).promise().catch(s=>{throw t.logger.error("[DynamoDB] transactWriteItems failed",s,{transactWriteParams:e}),s})),this.putDocumentToDynamo=(e=>{const{TableName:s}=e;return this.dynamodbDocumentClient.put(e).promise().catch(r=>{throw t.logger.error(`[DynamoDB] put to table [${s}] failed`,r,{putItemParams:e}),r})}),this.queryTable=(e=>{const{TableName:s}=e;return this.dynamodbDocumentClient.query(e).promise().catch(r=>{throw t.logger.error(`[DynamoDb] query to table [${s}] failed`,r,{queryTableParams:e}),r})}),this.scanTable=(e=>{const{TableName:s}=e;return this.dynamodbDocumentClient.scan(e).promise().catch(r=>{throw t.logger.error(`[DynamoDb] scan to table [${s}] failed`,r,{scanTableParams:e}),r})}),this.deleteItem=(e=>this.dynamodbDocumentClient.delete(e).promise().catch(s=>{throw t.logger.error("[DynamoDB]: Unable to delete item",s,{deleteItemParams:e}),s})),this.updateItem=(e=>this.dynamodbDocumentClient.update(e).promise().catch(s=>{throw t.logger.error("[DynamoDB]: Unable to update item",s,{updateItemParams:e}),s}));const r=f(e);this.dynamodb=new s.DynamoDB(r),this.dynamodbDocumentClient=new s.DynamoDB.DocumentClient({...r,convertEmptyValues:!0})}}const C=f();t.logger.debug("[DynamoDB]: Default client configuration",{defaultClientConfig:C});const E=new S(C);function A(e){return function(e,t,s){const r=new i(t,{statusCode:e});return K(r.output.payload,s)}(400,"Something went wrong while validating your payload",e)}function x(e,t={}){let s=t.statusCode;e instanceof N&&(s=e.httpStatusCode);const r=i.boomify(e,{...t,statusCode:s}),o={...r.output.payload,message:e.message||r.output.payload.message};return K(o)}function K(e,t){return t?{...e,errors:t}:e}class N extends Error{constructor(e,t,s){super(e),this.name=s,this.httpStatusCode=t,this.constructor=N,Error.captureStackTrace(this,N),Object.setPrototypeOf(this,N.prototype)}}class v extends N{constructor(e){super(e,500,"DatabaseException")}}class k extends N{constructor(e){super(e,404,"NotFoundException")}}class M extends N{constructor(e){super(e,403,"ForbiddenException")}}class U extends N{constructor(e){super(e,400,"BadRequestException")}}class q extends N{constructor(e){super(`Unreachable case: ${e}`,500,"UnreachableCaseError")}}class R{constructor(){this.errorServiceResponse=(e=>this.serviceResponseFromBoom(x(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 P=864e5;class _{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)/P)}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}}_.toDatetime=(e=>e.toISOString().slice(0,19).replace("T"," "));const{MONITORING_TABLE_NAME:$="monitoring"}=process.env,L="schedulerGSI",O={TableName:$,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:L,KeySchema:[{AttributeName:"scheduler",KeyType:"HASH"},{AttributeName:"sortKey",KeyType:"RANGE"}],Projection:{ProjectionType:"ALL"},ProvisionedThroughput:{ReadCapacityUnits:1,WriteCapacityUnits:1}}]};class F{constructor(e=E){this.createTable=(()=>this.dynamoClient.createTable(O)),this.alertFromDynamoItem=(e=>{const{userId:t}=F.decodeHashKey(e.hashKey),{monitorId:s,alertId:r}=this.decodeAlertSortKey(e.sortKey);return{userId:t,id:r,monitorId:s,created:_.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}=F.decodeHashKey(e.hashKey),{monitorId:s}=this.decodeMonitorSortKey(e.sortKey),r={userId:t,id:s,created:_.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 a.pickBy(r,e=>void 0!==e)}),this.dynamoClient=new D(O.TableName,e),this.dynamoRawClient=e,this.tableName=O.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 B={gettingStarted:{completedTasks:{}}},H="userInfo",Q=(e,t,s)=>({Key:{hashKey:F.encodeHashKey({userId:e}),sortKey:H},TableName:O.TableName,UpdateExpression:`set gettingStarted.completedTasks.${s} = :created`,ConditionExpression:`attribute_not_exists(gettingStarted.completedTasks.${s})`,ExpressionAttributeValues:{":created":_.getUnixTimestamp(t)}});class W{constructor(e=E){this.createTable=(()=>this.dynamoClient.createTable(O)),this.getUserInfo=(e=>{const t=F.encodeHashKey({userId:e});return this.dynamoClient.queryTable({KeyConditionExpression:"hashKey = :hashKey and sortKey = :sortKey",ExpressionAttributeValues:{":hashKey":t,":sortKey":H}}).then(t=>{if(!t.Items)throw new k(`Could not find userInfo for user: ${e}`);return t.Items.length>0?this.userInfoFromDynamoRow(t.Items[0]):this.insertUserInfoRow({hashKey:F.encodeHashKey({userId:e}),sortKey:H,...B})})}),this.completeGettingStartedTask=(e=>{const{taskName:s,completedOn:r,userId:o}=e;return this.dynamoClient.updateItem({...Q(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:F.encodeHashKey({userId:o}),sortKey:H,...t})}).catch(r=>{if(t.logger.error("Could not update completeGettingStartedTask",r,{params:e}),"ConditionalCheckFailedException"===r.name)throw new U(`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 k("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}=F.decodeHashKey(e.hashKey),s=a.mapValues(e.gettingStarted.completedTasks,_.dateFromUnixTimestamp);return{userId:t,gettingStarted:{...e.gettingStarted,completedTasks:s}}}),this.dynamoClient=new D(O.TableName,e)}}const G=new W,V=(e,t)=>`${e} object expected but got: \`${t.originalValue}\``;function j(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 z=/^[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 Y{}Y.isUUIDv4=(e=>z.test(e));const X=(e="id")=>c.string().test("isUUIDv4","uuid/v4 expected but got: `${value}`",e=>Y.isUUIDv4(e)).required(`${e} is required`),J=X(),Z=X("userId"),ee=c.object().shape({userId:Z,completedOn:c.date().required("created is required"),taskName:c.string().required("taskName is required")});class te extends R{constructor(e=E){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 j(ee,e)}(s);return r.error?this.serviceResponseFromBoom(A(r.errors)):this.tryExecute(async()=>{const e=await this.datasource.completeGettingStartedTask(s);return this.serviceResponse(e)})}),this.datasource=new W(e)}}const se=new te;class re{constructor(e){this.canAccessEntity=(e=>e.userId===this.userId),this.cannotAccessEntity=(e=>!this.canAccessEntity(e)),this.userId=e}}re.of=(e=>new re(e));const{STATUS_PAGE_TABLE_NAME:oe="status-page"}=process.env,ne="searchGSI",ie="customDomainGSI",ae={TableName:oe,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:ne,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 ce{constructor(e=E){this.createTable=(()=>this.dynamoClient.createTable(ae)),this.updateStatusPage=(e=>this.putStatusPage(e)),this.createStatusPage=(e=>{const t={id:d.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:ne,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 k(`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 k(`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:_.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:_.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 D(ae.TableName,e)}encodeId(e){return`id#${e}`}encodeUserId(e){return`userId#${e}`}decodeHashKey(e){const t=e.split("#");return t[1]}}const ue=new ce,he={userId:Z,niceName:c.string().required("niceName is required"),includeAllMonitors:c.boolean().required("includeAllMonitors is required"),monitors:c.array().of(c.string()),customDomain:c.string(),logo:c.string()},de=c.object().shape(he),me={...he,id:J},le=c.object().shape(me);class ye extends R{constructor(e=E){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 j(le,e)}(t);return s.error?this.serviceResponseFromBoom(A(s.errors)):this.tryExecute(async()=>{const s=await this.datasource.getStatusPage(t.id);if(re.of(e).cannotAccessEntity(s))throw new M("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(re.of(e).cannotAccessEntity(s))throw new M("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 j(de,e)}(t);return s.error?this.serviceResponseFromBoom(A(s.errors)):this.tryExecute(async()=>{if(re.of(e).cannotAccessEntity(t))throw new M("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 ce(e)}}const pe=new ye;class ge extends F{constructor(e=E){super(e),this.bookmarkMonitor=((e,t,s)=>{const r=F.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=F.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 k(`Could not find monitor id = ${t}`);return this.monitorFromDynamoItem(e.Items[0])})}),this.deleteMonitor=((e,t)=>{const s=F.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":F.encodeHashKey({userId:e}),":sortKey":"monitor"}}).then(this.mapDynamoRows)),this.getMonitorsByScheduler=(e=>this.dynamoClient.queryTable({KeyConditionExpression:"scheduler = :scheduler and begins_with(sortKey, :sortKey)",IndexName:L,ExpressionAttributeValues:{":scheduler":e,":sortKey":"monitor"}}).then(this.mapDynamoRows)),this.createMonitor=(async e=>{const t={...e,id:d.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=Q(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:ge.encodeHashKey(e),sortKey:this.encodeSortKey({monitorId:e.id}),name:e.name,body:a.get(e,"body"),scheduler:e.scheduler,apdex:a.get(e,"apdex"),endpoint:e.endpoint,headers:a.get(e,"headers"),isBookmarked:e.isBookmarked,location:e.location,regions:a.get(e,"regions"),type:e.type,created:_.getUnixTimestamp(e.created)})),this.mapDynamoRows=(e=>{const t=e.Items;return t.map(this.monitorFromDynamoItem)}),this.userDatasource=new W(e)}encodeSortKey({monitorId:e}){return`monitor#${e}`}}const be=new ge,we={endpoint:c.string().required("Endpoint is required so we know where to send our requests"),type:c.string().oneOf(["latency-check","https-check","certificate-check"]).required("Monitor type is required")},Ie=c.object().shape(we).typeError(e=>V("Executor",e));const Te=e=>V("Monitor",e),fe={...we,userId:Z,isBookmarked:c.boolean().required("isBookmarked is required"),scheduler:c.string().required("Scheduler is required, so we know how often to monitor your endpoint"),name:c.string().required("Name is required, so you can find your monitors easily")},De={id:J},Se=c.object().shape(fe).typeError(Te),Ce=c.object().shape({...fe,...De}).typeError(Te),Ee={...fe,body:c.string(),headers:c.object(),apdex:c.number().required("Apdex value is required so we can better measure performance of your endpoint"),regions:c.array().of(c.string()).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)},Ae=c.object().shape(Ee).typeError(Te),xe=c.object().shape({...Ee,...De}).typeError(Te);function Ke(e){return"latency-check"===e.type}class Ne extends R{constructor(e=E){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(re.of(e).cannotAccessEntity(s))throw new M("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 Ke(e)?j(xe,e):j(Ce,e)}(t);return s.error?this.serviceResponseFromBoom(A(s.errors)):this.tryExecute(async()=>{if(re.of(e).cannotAccessEntity(t))throw new M("You are not authorized to perform this action");const s=await this.datasource.getMonitor(e,t.id);if(s.type!==t.type)throw new U("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 Ke(e)?j(Ae,e):j(Se,e)}(t);return s.error?this.serviceResponseFromBoom(A(s.errors)):this.tryExecute(async()=>{if(re.of(e).cannotAccessEntity(t))throw new M("You are not authorized to perform this action");const s=await this.datasource.createMonitor(t);return{statusCode:201,body:s}})}),this.datasource=new ge(e)}}const ve=new Ne;class ke extends F{constructor(e=E){super(e),this.createAlert=(async e=>{const t={...e,id:d.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=Q(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=F.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=F.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 k(`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":F.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:F.encodeHashKey(e),sortKey:this.encodeSortKey({alertId:e.id,monitorId:e.monitorId}),scheduler:e.scheduler,created:_.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 W(e)}encodeSortKey({monitorId:e,alertId:t}){return`alert#${e}#${t}`}}const Me=new ke,Ue={userId:Z,monitorId:c.string().required("monitorId is required"),actionValue:c.string().required("actionValue is required"),value:c.number().required("value is required"),windowDuration:c.number().required("windowDuration is required"),type:c.string().oneOf(["downtime"]).required("Alert type is required"),operation:c.string().oneOf(["gt","lt"]).required("Alert operation is required"),action:c.string().oneOf(["email","webhook"]).required("Alert action is required")},qe=c.object().shape(Ue).typeError(e=>V("Alert",e));function Re(e){return j(qe,e)}const Pe="alert-any-monitor";class _e extends R{constructor(e=E){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 Re(s);return r.error?this.serviceResponseFromBoom(A(r.errors)):this.tryExecute(async()=>{const r=await this.getAlertById(e,t);if(re.of(e).cannotAccessEntity(r))throw new M("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 Re(t);return s.error?this.serviceResponseFromBoom(A(s.errors)):this.tryExecute(async()=>{if(re.of(e).cannotAccessEntity(t))throw new M("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 k(`Could not find alert id = ${t}`);return r}),this.getSchedulerByMonitorIdentifier=(async(e,t)=>{let s=Pe;if(t!==Pe){const r=await this.monitorDatasource.getMonitor(e,t);s=r.scheduler}return s}),this.alertDatasource=new ke(e),this.monitorDatasource=new ge(e)}}const $e=new _e;class Le extends F{constructor(e=E){super(e),this.isMonitor=(e=>e.sortKey.startsWith("monitor")),this.getWorkload=(e=>{const t=this.dynamoClient.queryTable({KeyConditionExpression:"scheduler = :scheduler",IndexName:L,ExpressionAttributeValues:{":scheduler":e}}),s=this.dynamoClient.queryTable({KeyConditionExpression:"scheduler = :scheduler",IndexName:L,ExpressionAttributeValues:{":scheduler":Pe}});return Promise.all([t,s]).then(([e,t])=>{const s=t.Items,r={[Pe]: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 Oe=new Le;const Fe="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36",Be={port:80,path:"/",method:"GET",headers:{"User-Agent":Fe}};class He{static hasRedirect(e){return He.isRedirectableStatus(e.statusCode)}static isRedirectableStatus(e){return void 0!==e&&(e>=300&&e<400)}}He.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)})})})(()=>He.get(e),t)),He.hasHttpsRedirect=(e=>{if(He.hasRedirect(e)){const t=e.headers.location;if(t&&"https:"===h.parse(t).protocol)return!0}return!1}),He.get=(e=>(He.isRequestOptionsAsObject(e)&&(e={...Be,...e}),new Promise((t,s)=>{r.get(e,e=>t(e)).on("error",e=>{s(e)})}))),He.isRequestOptionsAsObject=(e=>!a.isString(e));const Qe={port:443,path:"/",method:"GET",headers:{"User-Agent":Fe}},We=parseInt(process.env.EXECUTOR_TIMEOUT||"10000",10);class Ge extends R{constructor(e=m){super(),this.executeMonitorCheck=(async e=>{const t=await function(e){return j(Ie,e)}(e);return t.error?this.serviceResponseFromBoom(A(t.errors)):this.tryExecute(async()=>{const{endpoint:t,type:s,...r}=e,o=h.parse(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 q(s)}})}),this.fetch=e,this.certificateCache={}}latencyCheck({url:e,timeout:t=Ge.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=Ge.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={...Qe,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();a.isEmpty(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=Ge.DEFAULT_EXECUTOR_TIMEOUT){const s=e.protocol?e.hostname:e.href;return s?He.timeoutableGet({hostname:s},t).then(e=>{const t=He.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=_.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."}}}Ge.DEFAULT_EXECUTOR_TIMEOUT=We;const Ve=new Ge,je={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 ze{constructor(e=E){this.createTable=(()=>this.dynamoClient.createTable(je)),this.createConnection=(e=>{const t=d.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 D(je.TableName,e)}}const Ye=new ze;class Xe{}Xe.startTimer=(()=>y.performance.now());const Je={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 Ze{constructor(e){this.isConfig=(e=>void 0!==e.text),this.close=(()=>this.pool.end()),this.query=(e=>{Xe.startTimer();return this.pool.query(e).then(e=>e).catch(e=>{const s="[Postgres]: Something went wrong while executing query";throw t.logger.error(s,e),new v(`${s}: ${e.message}`)})}),this.pool=e}}const et=new Ze(new l.Pool(Je)),tt="checks";class st{constructor(e=et){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 ${tt}.${r} WHERE ${tt}.${r}.monitor_id = $1 `;if(s){const{from:e,to:t}=s;o=`${o} AND ${tt}.${r}.time BETWEEN '${_.toDatetime(e)}'::timestamp AND ${t?`'${_.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 rt="latency",ot=`CREATE TABLE ${tt}.${rt} (\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);`,nt=`SELECT create_hypertable('${tt}.${rt}', 'time');`;class it extends st{constructor(e=et){super(e),this.createTable=(()=>this.client.query(ot).then(e=>this.client.query(nt))),this.getLatencyChecks=(e=>this.getMonitorChecks({...e,tableName:rt})),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=a.get(r,"timings",{}),i=r.blobSize,c=r.error;return{name:"insert-latency-check",text:`INSERT INTO ${tt}.${rt} `+"(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,c,n.totalTime,n.tlsHandshakeTime,n.tcpConnectionTime,n.dnsLookupTime,n.firstByteTime,i]}}}new it;class at{constructor(e=et){this.createChecksSchema=(()=>this.client.query({name:"create-schema",text:`CREATE SCHEMA ${tt};`})),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 q(t)}}),this.getLatency=(()=>this.latency),this.latency=new it(e),this.client=e}}const ct=new at;class ut extends R{constructor(e=et,t=E){super(),this.getChecks=((e,t,s={limit:20})=>this.tryExecute(async()=>{const r=await this.monitorDatasource.getMonitor(e,t);if(re.of(e).cannotAccessEntity(r))throw new M("You are not authorized to perform this action");if(Ke(r))return this.latencyCheckDatasource.getLatencyChecks({monitorId:t,pagination:s}).then(this.serviceResponse);throw new Error("Not implemented")}));const s=new at(e);this.latencyCheckDatasource=s.getLatency(),this.monitorDatasource=new ge(t)}}const ht=new ut;class dt{constructor(e){this.getQueueURL=(e=>this.sqs.getQueueUrl({QueueName:e}).promise()),this.sendMessage=(e=>(t.logger.debug("[SQS]: sending message",{params:e}),this.sqs.sendMessage(e).promise().catch(s=>{throw t.logger.error("[SQS]: failed to send message",s,{params:e}),s}))),this.sendMessageBatch=(e=>(t.logger.debug("[SQS]: sending message batch",{params:e}),this.sqs.sendMessageBatch(e).promise().catch(s=>{throw t.logger.error("[SQS]: failed to send message batch",s,{params:e}),s}))),this.receiveMessage=(e=>(t.logger.debug("[SQS]: receiving message",{params:e}),this.sqs.receiveMessage(e).promise().catch(s=>{throw t.logger.error("[SQS]: failed to receive message",s,{params:e}),s}))),this.createQueue=(e=>(t.logger.info("[SQS]: creating queue",{params:e}),this.sqs.createQueue(e).promise().catch(s=>{throw t.logger.error("[SQS]: failed to create queue",s,{params:e}),s}))),this.sqs=e}}const mt=function(e={}){const{AWS_SQS_ENDPOINT:t}=process.env;let s=e.endpoint||t;return w||s||(s="http://localhost:9324"),T({...e,endpoint:s})}();t.logger.debug("[SQS]: Default client configuration",{defaultSqsConfiguration:mt});const lt=new s.SQS(mt);class yt{constructor(e,s=lt){this.getQueueUrl=(()=>this.QueueUrl?Promise.resolve(this.QueueUrl):this.sqsClient.getQueueURL(this.QueueName).then(({QueueUrl:e})=>{if(!e)throw new Error(`[SQS]: Could not getQueueUrl for ${this.QueueName}`);return t.logger.debug(`[SQS]: queue ${e} warmed up`),this.QueueUrl=e,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=e,this.sqsClient=new dt(s)}async queueParams(e){const t=await this.getQueueUrl();return{...e,QueueUrl:t}}}exports.AlertService=_e,exports.AlertViolationNotifier=class{constructor(e=lt){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 yt(this.QUEUE_NAME,e)}},exports.DnsService=class extends R{constructor(){super(...arguments),this.cnameLookup=(e=>{const t=h.parse(e),s=(t.protocol?t.hostname:t.href)||"";return this.tryExecute(()=>new Promise((e,t)=>{u.resolveCname(s,(s,r)=>{s?t(new Error(s.message)):e(this.serviceResponse(r))})}))})}},exports.DynamoAlertDatasource=ke,exports.DynamoConnectionDatasource=ze,exports.DynamoDBClient=S,exports.DynamoExecutorsWorkloadDatasource=Le,exports.DynamoMonitorDatasource=ge,exports.DynamoStatusPageDatasource=ce,exports.DynamoUserDatasource=W,exports.EmailNotificationService=class{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}},exports.MonitorChecksDatasource=at,exports.MonitorChecksService=ut,exports.MonitorExecutorService=Ge,exports.MonitorsService=Ne,exports.PostgresClient=Ze,exports.SQSQueue=yt,exports.StatusPageService=ye,exports.UserService=te,exports.alertService=$e,exports.boomify=x,exports.dynamoAlertDatasource=Me,exports.dynamoConnectionDatasource=Ye,exports.dynamoExecutorsWorkloadDatasource=Oe,exports.dynamoMonitorDatasource=be,exports.dynamoStatusPageDatasource=ue,exports.dynamoUserDatasource=G,exports.initializeTables=async function(e=E,t=et){await async function(e){const t=[O,je,ae];return await Promise.all(t.map(e.createTable))}(e),await async function(e){const t=new at(e);await t.createChecksSchema(),await t.getLatency().createTable()}(t)},exports.isBoom=function(e){const t=e;return void 0!==t.error},exports.isLatencyCheckMonitor=Ke,exports.monitorChecksDatasource=ct,exports.monitorChecksService=ht,exports.monitorExecutorService=Ve,exports.monitorsService=ve,exports.statusPageService=pe,exports.timescaleMonitorCheckDatasource=st,exports.userService=se;
//# sourceMappingURL=meshwatchbackend-core.cjs.production.js.map