-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: init db wrapper #7
Changes from 25 commits
057be2d
9d0971b
95ff60d
2418fb3
63e21cc
b71d19b
e678ad8
fabbd71
83e34ce
2f90385
c15a214
a4a362d
e673748
3271e0f
0cbdaa6
930c617
306bf56
9734949
221dfe4
912dd28
71285f7
a05ac6f
db9a7d4
42dee05
a873045
56f314b
06dc51f
e14cce0
b4ec36c
3a2180a
0360407
a506edf
f4992fb
efbf8ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,4 @@ logs | |
test-results.xml | ||
.env | ||
.idea/ | ||
.npmrc |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,25 +9,17 @@ | |
* OF ANY KIND, either express or implied. See the License for the specific language | ||
* governing permissions and limitations under the License. | ||
*/ | ||
const log = (level, message, ...args) => { | ||
const timestamp = new Date().toISOString(); | ||
|
||
switch (level) { | ||
case 'info': | ||
console.info(`[${timestamp}] INFO: ${message}`, ...args); | ||
break; | ||
case 'error': | ||
console.error(`[${timestamp}] ERROR: ${message}`, ...args); | ||
break; | ||
case 'warn': | ||
console.warn(`[${timestamp}] WARN: ${message}`, ...args); | ||
break; | ||
default: | ||
console.log(`[${timestamp}] ${message}`, ...args); | ||
break; | ||
} | ||
}; | ||
'use strict'; | ||
|
||
export { | ||
log, | ||
}; | ||
import DB from './db.js'; | ||
|
||
export default function dynamoDBWrapper(func) { | ||
return async (request, context) => { | ||
if (!context.db) { | ||
context.db = new DB(context); | ||
} | ||
Comment on lines
+17
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tripodsan we were following the pattern from storage which is included directly in the context https://github.com/adobe/helix-universal/blob/7a63f8ddc179d5f91c1c170b40bf4bad4a17e769/src/aws-adapter.js#L158, and it s not an attribute. Sure we will cleanup after use. |
||
|
||
return func(request, context); | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -11,47 +11,51 @@ | |||||
*/ | ||||||
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; | ||||||
import { DynamoDBDocumentClient, GetItemCommand, PutCommand } from '@aws-sdk/lib-dynamodb'; | ||||||
import { log } from './util.js'; | ||||||
|
||||||
const TABLE_SITES = 'spacecat-site'; | ||||||
const TABLE_AUDITS = 'spacecat-audit-index'; | ||||||
|
||||||
function DB(config) { | ||||||
const client = new DynamoDBClient({ region: config.region }); | ||||||
const docClient = DynamoDBDocumentClient.from(client); | ||||||
export default class DB { | ||||||
constructor(context) { | ||||||
this.client = new DynamoDBClient({ region: context.runtime.region }); | ||||||
this.log = context.log; | ||||||
this.docClient = DynamoDBDocumentClient.from(this.client); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Save a record to the DynamoDB. | ||||||
* @param {object} record - The new record to save. | ||||||
* @param tableName - The name of the table to save the record to. | ||||||
*/ | ||||||
async function saveRecord(record, tableName) { | ||||||
async saveRecord(record, tableName) { | ||||||
try { | ||||||
const command = new PutCommand({ | ||||||
TableName: tableName, | ||||||
Item: record, | ||||||
}); | ||||||
await docClient.send(command); | ||||||
await this.docClient.send(command); | ||||||
} catch (error) { | ||||||
log('error', 'Error saving record: ', error); | ||||||
this.log.error(`Error saving record: ${error}`); | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* Saves an audit to the DynamoDB. | ||||||
* @param {object} site - Site object containing details of the audited site. | ||||||
* @param {object} audit - Audit object containing the type and result of the audit. | ||||||
* @returns {Promise<void>} Resolves once audit is saved. | ||||||
*/ | ||||||
async function saveAuditIndex(site, audit) { | ||||||
* Saves an audit to the DynamoDB. | ||||||
* @param {object} site - Site object containing details of the audited site. | ||||||
* @param {object} audit - Audit object containing the type and result of the audit. | ||||||
* @returns {Promise<void>} Resolves once audit is saved. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not true.... returns the new adit object |
||||||
*/ | ||||||
async saveAuditIndex(site, audit) { | ||||||
const now = new Date().toISOString(); | ||||||
const uuid = Date.now().toString(); | ||||||
const uuid = Date.now() | ||||||
.toString(); | ||||||
|
||||||
const newAudit = { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe it would help to include a typedef for either as jsdoc comment |
||||||
id: uuid, | ||||||
siteId: site.id, | ||||||
siteId: `${site.domain}/${site.path}`, | ||||||
audit_date: now, | ||||||
type: 'psi', | ||||||
is_live: site.isLive, | ||||||
is_live: false, | ||||||
content_publication_date: '', | ||||||
git_hashes: [], | ||||||
tag_manager: '', | ||||||
|
@@ -77,17 +81,17 @@ function DB(config) { | |||||
}, | ||||||
], | ||||||
}; | ||||||
log('info', `Audit for domain ${site.domain} saved successfully at ${now}`); | ||||||
await saveRecord(newAudit, TABLE_AUDITS); | ||||||
return newAudit; | ||||||
await this.saveRecord(newAudit, TABLE_AUDITS); | ||||||
this.log.info(`Audit for domain ${site.domain} saved successfully at ${now}`); | ||||||
return Promise.resolve(newAudit); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the return type of a
Suggested change
|
||||||
} | ||||||
|
||||||
/** | ||||||
* Save an error that occurred during a Lighthouse audit to the DynamoDB. | ||||||
* @param {object} site - site audited. | ||||||
* @param {Error} error - The error that occurred during the audit. | ||||||
*/ | ||||||
async function saveAuditError(site, error) { | ||||||
* Save an error that occurred during a Lighthouse audit to the DynamoDB. | ||||||
* @param {object} site - site audited. | ||||||
* @param {Error} error - The error that occurred during the audit. | ||||||
*/ | ||||||
async saveAuditError(site, error) { | ||||||
const now = new Date().toISOString(); | ||||||
const newAudit = { | ||||||
siteId: site.id, | ||||||
|
@@ -96,15 +100,17 @@ function DB(config) { | |||||
error: error.message, | ||||||
scores: {}, | ||||||
}; | ||||||
await saveRecord(newAudit, TABLE_AUDITS); | ||||||
await this.saveRecord(newAudit, TABLE_AUDITS); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Fetches a site by its ID and gets its latest audit. | ||||||
* @param {string} siteId - The ID of the site to fetch. | ||||||
* @returns {Promise<object>} Site document with its latest audit. | ||||||
*/ | ||||||
async function getSite(domain, path) { | ||||||
const params = { | ||||||
* Fetches a site by its ID and gets its latest audit. | ||||||
* @param {string} domain - The domain of the site to fetch. | ||||||
* @param {string} path - The path of the site to fetch. | ||||||
* @returns {Promise<object>} Site document with its latest audit. | ||||||
*/ | ||||||
async getSite(domain, path) { | ||||||
const commandParams = { | ||||||
TableName: TABLE_SITES, // Replace with your table name | ||||||
Key: { | ||||||
Domain: { S: domain }, // Partition key | ||||||
|
@@ -113,26 +119,19 @@ function DB(config) { | |||||
}; | ||||||
|
||||||
try { | ||||||
const command = new GetItemCommand(params); | ||||||
const response = await client.send(command); | ||||||
const command = new GetItemCommand(commandParams); | ||||||
const response = await this.client.send(command); | ||||||
const item = response.Item; | ||||||
if (item) { | ||||||
log('info', `Item retrieved successfully: ${item}`); | ||||||
this.log.info(`Item retrieved successfully: ${item}`); | ||||||
return item; | ||||||
} else { | ||||||
log('info', 'Item not found.'); | ||||||
this.log.info('Item not found.'); | ||||||
return null; | ||||||
} | ||||||
} catch (error) { | ||||||
log('error', `Error ${error}`); | ||||||
this.log.error(`Error ${error}`); | ||||||
throw error; | ||||||
} | ||||||
} | ||||||
return { | ||||||
getSite, | ||||||
saveAuditIndex, | ||||||
saveAuditError, | ||||||
}; | ||||||
} | ||||||
|
||||||
export default DB; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,6 @@ | |
* governing permissions and limitations under the License. | ||
*/ | ||
import axios from 'axios'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would be cool to use |
||
import { log } from './util.js'; | ||
|
||
function PSIClient(config) { | ||
const AUDIT_TYPE = 'PSI'; | ||
|
@@ -136,6 +135,7 @@ function PSIClient(config) { | |
* @returns {Promise<object>} The processed PageSpeed Insights audit data. | ||
*/ | ||
const performPSICheck = async (domain, strategy) => { | ||
// eslint-disable-next-line no-useless-catch | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not follow the eslint advice here ? :-) |
||
try { | ||
const apiURL = getPSIApiUrl(domain, strategy); | ||
const { data: lhs } = await axios.get(apiURL); | ||
|
@@ -144,7 +144,6 @@ function PSIClient(config) { | |
|
||
return processLighthouseResult(lighthouseResult); | ||
} catch (e) { | ||
log('error', `Error happened during PSI check: ${e}`); | ||
throw e; | ||
} | ||
}; | ||
|
@@ -153,12 +152,8 @@ function PSIClient(config) { | |
const auditResults = {}; | ||
|
||
for (const strategy of PSI_STRATEGIES) { | ||
const strategyStartTime = process.hrtime(); | ||
// eslint-disable-next-line no-await-in-loop | ||
const psiResult = await performPSICheck(domain, strategy); | ||
const strategyEndTime = process.hrtime(strategyStartTime); | ||
const strategyElapsedTime = (strategyEndTime[0] + strategyEndTime[1] / 1e9).toFixed(2); | ||
log('info', `Audited ${domain} for ${strategy} strategy in ${strategyElapsedTime} seconds`); | ||
|
||
auditResults[strategy] = psiResult; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/* | ||
* Copyright 2023 Adobe. All rights reserved. | ||
* This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. You may obtain a copy | ||
* of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under | ||
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
* OF ANY KIND, either express or implied. See the License for the specific language | ||
* governing permissions and limitations under the License. | ||
*/ | ||
|
||
'use strict'; | ||
alinarublea marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
import SqsQueue from './sqs-queue.js'; | ||
|
||
export default function queueWrapper(func) { | ||
return async (request, context) => { | ||
const queueUrl = context.env.AUDIT_RESULTS_QUEUE_URL; | ||
|
||
if (!queueUrl) { | ||
throw new Error('AUDIT_RESULTS_QUEUE_URL env variable is empty/not provided'); | ||
} | ||
|
||
context.attributes.queueUrl = queueUrl; | ||
|
||
if (!context.queue) { | ||
context.queue = new SqsQueue(context); | ||
} | ||
Comment on lines
+26
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also use |
||
|
||
return func(request, context); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. probably close/discard queue client ? |
||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,47 +9,44 @@ | |
* OF ANY KIND, either express or implied. See the License for the specific language | ||
* governing permissions and limitations under the License. | ||
*/ | ||
import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs'; | ||
import { SendMessageCommand, SQSClient } from '@aws-sdk/client-sqs'; | ||
|
||
// Set up the region | ||
const REGION = 'us-east-1'; // change this to your desired region | ||
/** | ||
* @class SQSQueue class to send audit results to SQS | ||
* @param {string} region - AWS region | ||
* @param {string} queueUrl - SQS queue URL | ||
* @param {object} log - OpenWhisk log object | ||
*/ | ||
export default class SQSQueue { | ||
constructor(context) { | ||
const { region, log } = context; | ||
const { queueUrl } = context.attributes; | ||
|
||
// Create SQS service client object | ||
const sqsClient = new SQSClient({ region: REGION }); | ||
this.queueUrl = queueUrl; | ||
this.log = log; | ||
|
||
// Your SQS queue URL | ||
const queueURL = 'https://sqs.us-east-1.amazonaws.com/282898975672/spacecat-audit-results'; | ||
this.sqsClient = new SQSClient({ region }); | ||
log.info(`Creating SQS client in region ${region}`); | ||
} | ||
|
||
function SQSQueue() { | ||
async function sendMessage(message) { | ||
async sendAuditResult(message) { | ||
const body = { | ||
message, | ||
timestamp: new Date().toISOString(), | ||
}; | ||
|
||
// Set up the parameters for the send message command | ||
const params = { | ||
DelaySeconds: 10, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is this delay for? |
||
MessageBody: JSON.stringify(body), | ||
QueueUrl: queueURL, | ||
QueueUrl: this.queueUrl, | ||
}; | ||
|
||
try { | ||
const data = await sqsClient.send(new SendMessageCommand(params)); | ||
console.log('Success, message sent. MessageID:', data.MessageId); | ||
const data = await this.sqsClient.send(new SendMessageCommand(params)); | ||
this.log.info(`Success, message sent. MessageID: ${data.MessageId}`); | ||
} catch (err) { | ||
console.log('Error:', err); | ||
this.log.error(`Error: ${err}`); | ||
throw err; | ||
} | ||
|
||
return { | ||
statusCode: 200, | ||
body: JSON.stringify({ message: 'SQS message sent!' }), | ||
}; | ||
} | ||
return { | ||
sendMessage, | ||
}; | ||
} | ||
|
||
export default SQSQueue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not needed. ESM is always strict.