Skip to content

Commit

Permalink
initial-setup of aws
Browse files Browse the repository at this point in the history
  • Loading branch information
alinarublea committed Oct 10, 2023
1 parent 59f4fc8 commit 05df4bb
Show file tree
Hide file tree
Showing 8 changed files with 525 additions and 6 deletions.
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,11 @@
"lint-staged": {
"*.js": "eslint",
"*.cjs": "eslint"
},
"dependencies": {
"aws-sdk": "2.1472.0",
"@adobe/helix-shared-wrap": "2.0.0",
"@adobe/helix-status": "10.0.10",
"@adobe/helix-universal-logger": "3.0.11"
}
}
}
120 changes: 120 additions & 0 deletions src/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* 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.
*/
const AWS = require('aws-sdk');
const { log } = require('./util.js');

const TABLE_SITES = 'sites';
const TABLE_AUDITS = 'audits';

function DB(config) {
const { region, accessKeyId, secretAccessKey } = config;

AWS.config.update({ region, accessKeyId, secretAccessKey });
const dynamoDb = new AWS.DynamoDB.DocumentClient();

/**
* Save an audit record to the DynamoDB.
* @param {object} newAudit - The new audit record to save.
*/
async function saveAuditIndexRecord(newAudit) {
try {
const params = {
TableName: TABLE_AUDITS,
Item: newAudit,
};
await dynamoDb.put(params).promise();
} catch (error) {
log('error', 'Error saving audit: ', 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) {
const now = new Date().toISOString();
const newAudit = {
siteId: site.id,
auditDate: now,
error: '',
isLive: site.isLive,
scores: {
mobile: {
performance: audit.lighthouseResults.categories.performance.score,
seo: audit.lighthouseResults.categories.seo.score,
'best-practices': audit.lighthouseResults.categories['best-practices'].score,
accessibility: audit.lighthouseResults.categories.accesibility.score,
},
},
};
await saveAuditIndexRecord(newAudit);
log('info', `Audit for domain ${site.domain} saved successfully at ${now}`);
}
/**
* 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) {
const now = new Date().toISOString();
const newAudit = {
siteId: site.id,
auditDate: now,
isLive: site.isLive,
error: error.message,
scores: {},
};
await saveAuditIndexRecord(newAudit);
}
/**
* 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 findSiteById(siteId) {
try {
const siteParams = {
TableName: TABLE_SITES,
Key: {
id: siteId,
},
};
const siteResult = await dynamoDb.get(siteParams).promise();
if (!siteResult.Item) return null;
const auditParams = {
TableName: TABLE_AUDITS,
KeyConditionExpression: 'siteId = :siteId',
ExpressionAttributeValues: {
':siteId': siteId,
},
Limit: 1,
ScanIndexForward: false, // get the latest audit
};
const auditResult = await dynamoDb.query(auditParams).promise();
// eslint-disable-next-line prefer-destructuring
siteResult.Item.latestAudit = auditResult.Items[0];

return siteResult.Item;
} catch (error) {
console.error('Error getting site by site id:', error.message);
return Error(error.message);
}
}
return {
findSiteById,
saveAuditIndex,
saveAuditError,
};
}
module.exports = DB;
37 changes: 37 additions & 0 deletions src/edge-delivery-service-admin-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.
*/
const BASE_URL = 'https://www.hlx.live/';
/**
* Retrieve the status of the admin endpoint.
* @returns {Promise<string>} - The lastModification property.
* @throws {Error} - Throws an error if there's a network issue or some other error while fetching data.
*/
function EdgeDeliveryServiceAdminClient() {
const getLastModification = async () => {
try {
const response = await fetch(`${BASE_URL}docs/status.json`);
if (!response.ok) {
throw new Error(`Failed to fetch status: ${response.statusText}`);
}
const data = await response.json();
if (!data || !data.lastModification) {
throw new Error('lastModification property not found in response data');
}
return data.lastModification;
} catch (error) {
console.error('Error fetching lastModification:', error);
throw error;
}
};
}

module.exports = EdgeDeliveryServiceAdminClient;
103 changes: 103 additions & 0 deletions src/github-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const axios = require('axios');
const { log } = require('./util.js');

const SECONDS_IN_A_DAY = 86400; // Number of seconds in a day

function GithubClient(config) {
const {baseUrl, githubId, githubSecret} = config;

/**
* Creates a URL for the GitHub API.
*
* @param {string} githubOrg - The name of the GitHub organization.
* @param {string} repoName - The name of the repository (optional).
* @param {string} path - Additional path (optional).
* @param {number} page - The page number for pagination (optional).
* @returns {string} The created GitHub API URL.
*/
function createGithubApiUrl(githubOrg, repoName = '', path = '', page = 1) {
const repoPart = repoName ? `/${repoName}` : '';
const pathPart = path ? `/${path}` : '';

return `${baseUrl}/repos/${githubOrg}${repoPart}${pathPart}?page=${page}&per_page=100`;
}

/**
* Creates a Basic Authentication header value from a given GitHub ID and secret.
*
* @returns {string} - The Basic Authentication header value.
* @throws {Error} - Throws an error if GitHub credentials are not provided.
*/
function createGithubAuthHeaderValue() {
if (!githubId || !githubSecret) {
throw new Error('GitHub credentials not provided');
}
return `Basic ${Buffer.from(`${githubId}:${githubSecret}`).toString('base64')}`;
}

/**
* Fetches the SHAs of all commits made in a GitHub repository between two date-times using the GitHub API.
*
* @async
* @function
* @param {object} domain - The domain of the audited site.
* @param {string} latestAuditTime - The end date-time in ISO format (e.g. 'YYYY-MM-DDTHH:mm:ss.sssZ').
* @param {string} lastAuditedAt - The start date-time in ISO format (e.g. 'YYYY-MM-DDTHH:mm:ss.sssZ'). If not provided, it defaults to 24 hours before the end date-time.
* @param {string} gitHubURL - The URL of the GitHub repository from which the SHAs will be fetched (e.g. 'https://github.com/user/repo').
* @returns {Promise<string[]>} A promise that resolves to an array of SHAs of commits between the given date-times. If there's an error fetching the data, the promise resolves to an empty array.
* @throws {Error} Will throw an error if there's a network issue or some other error while fetching data from the GitHub API.
* @example
* fetchGithubCommitsSHA(
* { gitHubURL: 'https://github.com/myOrg/myRepo', lastAudited: '2023-06-15T00:00:00.000Z' },
* { result: { fetchTime: '2023-06-16T00:00:00.000Z' } },
* 'yourGithubId',
* 'yourGithubSecret'
* ).then(SHAs => console.log(SHAs));
*/
async function fetchGithubCommitsSHA(domain, latestAuditTime, lastAuditedAt, gitHubURL) {
if (!gitHubURL) {
log('info', `No github repo defined for site ${domain}. Skipping github SHA retrieval`);
return [];
}

try {
const until = new Date(latestAuditTime);
const since = lastAuditedAt ? new Date(lastAuditedAt) : new Date(until - SECONDS_IN_A_DAY * 1000); // 24 hours before until
const repoPath = new URL(gitHubURL).pathname.slice(1); // Removes leading '/'

log('info', `Fetching SHAs for domain ${domain} with repo ${repoPath} between ${since.toISOString()} and ${until.toISOString()}`);

const [githubOrg, repoName] = repoPath.split('/');

const authHeader = createGithubAuthHeaderValue();
const commitsUrl = createGithubApiUrl(githubOrg, repoName, 'commits');

const response = await axios.get(commitsUrl, {
params: {
since: since.toISOString(),
until: until.toISOString()
},
headers: {
Authorization: authHeader
}
});

const commitSHAs = response.data.map(commit => commit.sha);

log('info', `Found ${commitSHAs.length} commits for site ${domain}.`);

return commitSHAs;
} catch (error) {
log('error', `Error fetching GitHub SHAs for site ${domain}:`, error.response ? error.response.data : error);
return [];
}
}

return {
createGithubApiUrl,
createGithubAuthHeaderValue,
fetchGithubCommitsSHA
}
}

module.exports = GithubClient;
22 changes: 17 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Adobe. All rights reserved.
* Copyright 2019 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
Expand All @@ -9,12 +9,24 @@
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import wrap from '@adobe/helix-shared-wrap';
import { logger } from '@adobe/helix-universal-logger';
import { helixStatus } from '@adobe/helix-status';
import { Response } from '@adobe/fetch';

/**
* This is the main function
* @param {string} name name of the person to greet
* @returns {string} a greeting
* @param {Request} request the request object (see fetch api)
* @param {UniversalContext} context the context of the universal serverless function
* @returns {Response} a response
*/
export function main(name = 'world') {
return `Hello, ${name}.`;
function run(request, context) {
const name = new URL(request.url).searchParams.get('name') || 'world';
context.log.info(`Saying hello to: ${name}.`);
return new Response(`Hello, ${name}.`);
}

export const main = wrap(run)
.with(helixStatus)
.with(logger.trace)
.with(logger);
Loading

0 comments on commit 05df4bb

Please sign in to comment.