Skip to content

Commit

Permalink
Merge pull request #182 from southworks/dev
Browse files Browse the repository at this point in the history
[Feature] Multi-language support and automated PR creation for Application/Game contribution
  • Loading branch information
pcolmer authored Dec 11, 2024
2 parents 288cfbd + 6830ec1 commit 4a0bfdf
Show file tree
Hide file tree
Showing 76 changed files with 5,205 additions and 274 deletions.
28 changes: 27 additions & 1 deletion .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,34 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"

- name: Install Lambda dependencies
run: |
npm install
working-directory: aws-lambdas

- name: Structure Lambda AppForm
run: |
mkdir -p AppForm
shopt -s extglob
cp -r !(AppForm|GameForm) AppForm/
working-directory: aws-lambdas

- name: Structure Lambda GameForm
run: |
mkdir -p GameForm
shopt -s extglob
cp -r !(AppForm|GameForm) GameForm/
working-directory: aws-lambdas

- name: Build and deploy
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
GITHUB_BASE_BRANCH: ${{ vars.GH_BASE_BRANCH }}
GITHUB_OWNER: ${{ vars.GH_OWNER }}
GITHUB_REPO: ${{ vars.GH_REPO }}
RECAPTCHA_V2_VERIFY_URL: ${{ vars.PUBLIC_CAPTCHA_WEB_SITE_KEY }}
RECAPTCHA_V2_SECRET_KEY: ${{ secrets.RECAPTCHA_V2_SECRET_KEY }}
run: |
# Set up the environment variables
export NODE_OPTIONS=--experimental-wasm-modules
Expand Down Expand Up @@ -78,4 +104,4 @@ jobs:
if [[ "${{ github.ref_name }}" == "main" ]]; then
export STAGE="main"
fi
yarn sst deploy --stage $STAGE
yarn sst deploy --stage $STAGE
12 changes: 12 additions & 0 deletions astro-i18next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { DEFAULT_LOCALE, LOCALES } from "./src/config/i18nConfig.js";

export default {
defaultLocale: DEFAULT_LOCALE,
locales: LOCALES,
load: ["server", "client"],
resourcesBasePath: "/locales",
showDefaultLocale: true,
i18nextServer: {
debug: true,
}
};
13 changes: 11 additions & 2 deletions astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { defineConfig } from "astro/config";
import astroI18next from "astro-i18next";
import aws from "astro-sst";
import sitemap from "@astrojs/sitemap";
import tailwind from "@astrojs/tailwind";
import solidJs from "@astrojs/solid-js";
import pagefind from "./integrations/pagefind";
import auth from "./integrations/auth";
import { loadEnv } from "vite";

import { LOCALES, DEFAULT_LOCALE } from "./src/config/i18nConfig";

const { IS_PUBLIC, PRE_BUILD, CUSTOM_DOMAIN } = loadEnv(
process.env.NODE_ENV,
Expand All @@ -32,6 +33,7 @@ export default defineConfig({
applyBaseStyles: false,
}),
solidJs(),
astroI18next(),
],
}
: {
Expand Down Expand Up @@ -61,9 +63,16 @@ export default defineConfig({
rollupOptions: {
external: ["/pagefind/pagefind.js"],
},
redirects: false,
},
vite: {
optimizeDeps: { exclude: ['auth:config'] },
},
i18n: {
defaultLocale: DEFAULT_LOCALE,
locales: LOCALES,
routing: {
prefixDefaultLocale: true,
redirectToDefaultLocale: true
}
}
});
15 changes: 15 additions & 0 deletions aws-lambdas/.env_template
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
GITHUB_TOKEN=<your-github-pat-token>
GITHUB_OWNER=<owner>
GITHUB_REPO=<repo>
GITHUB_BASE_BRANCH=<base_branch>
GITHUB_APP_ID=<github_app_id>
GITHUB_APP_INSTALLATION_ID=<github_app_id>
GITHUB_APP_PK_BASE64="<base-64-encoded-github-app-private-key"
RECAPTCHA_V2_VERIFY_URL="https://www.google.com/recaptcha/api/siteverify"
RECAPTCHA_V2_SECRET_KEY="<recaptcha-v2-secret-key>"
SENDGRID_API_KEY="<sendgird-api-key>"
SENDER_EMAIL="<sender-email>"
RECIPIENT_EMAIL="<recipient-email>"
ALLOWED_REFERER="<allowed-referer>"
SECRET_NAME="<secret_name>"
AWS_SECRET_REGION="<aws_region>"
80 changes: 80 additions & 0 deletions aws-lambdas/AppForm/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import RequestValidator from './utils/request-validator.js';
import EmailManager from './utils/email-manager.js';
import ConfigManager from './utils/config-manager.js';
import PullRequestManager from './utils/pull-request-manager.js';
import IssueManager from './utils/issue-manager.js';
import { FileType } from "./models/file-type.js";

const configManager = new ConfigManager();
let config;

const HEADERS = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};

const RequestTypes = Object.freeze({
RequestThisAppToBeTested: "requestThisAppToBeTested",
SubmitNewOrUpdatedAppInfo: "submitNewOrUpdatedAppInfo"
});

export const handler = async (event) => {
console.log('Received event:', JSON.stringify(event, null, 2));

try {
const formData = JSON.parse(event.body);

const refererError = RequestValidator.validateReferer(event.headers, process.env['ALLOWED_REFERER']);
if(!refererError) config = await configManager.loadConfig(FileType.Application);
const validationError = refererError === "" ? await RequestValidator.validateRequest(formData, FileType.Application, config.recaptchaV2VerifyUrl, config.recaptchaV2SecretKey) : 'Access forbidden';
if (validationError) {
return { statusCode: validationError === 'Access forbidden' ? 403 : 400, headers: HEADERS, body: validationError };
}

switch (formData.requestType) {
case RequestTypes.RequestThisAppToBeTested:
await handleTestingRequest(formData);
break;
case RequestTypes.SubmitNewOrUpdatedAppInfo:
await handleSubmitOrUpdateRequest(formData);
break;
default:
return { statusCode: 400, headers: HEADERS, body: `Invalid request type: ${formData.requestType}` };
}

return { statusCode: 200, headers: HEADERS, body: 'Successfully processed request' };
} catch (error) {
return handleError(error);
}
};

const handleTestingRequest = async (data) => {
const issueManager = new IssueManager();
console.log(`Creating Issue for ${data.name} from ${data.publisher}.`);
await issueManager.createIssue(data, {
githubAppId: config.githubAppId,
githubAppInstallationId: config.githubAppInstallationId,
githubAppPkBase64: config.githubAppPkBase64,
githubOwner: config.githubOwner,
githubRepo: config.githubRepo
});
console.log(`Issue for ${data.name} from ${data.publisher} successfully created.`);
};

const handleSubmitOrUpdateRequest = async (data) => {
const pullRequestManager = new PullRequestManager();
await pullRequestManager.createPullRequest(data, {
githubAppId: config.githubAppId,
githubAppInstallationId: config.githubAppInstallationId,
githubAppPkBase64: config.githubAppPkBase64,
githubOwner: config.githubOwner,
githubRepo: config.githubRepo,
githubBaseBranch: config.githubBaseBranch
}, FileType.Application);
};

const handleError = (error) => {
console.error(error);
return { statusCode: 500, headers: HEADERS, body: "An error occurred while processing your form submission." };
};
51 changes: 51 additions & 0 deletions aws-lambdas/GameForm/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import RequestValidator from './utils/request-validator.js';
import ConfigManager from './utils/config-manager.js';
import PullRequestManager from './utils/pull-request-manager.js';
import { FileType } from "./models/file-type.js";

const configManager = new ConfigManager();
let config;

const HEADERS = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};

export const handler = async (event) => {
console.log('Received event:', JSON.stringify(event, null, 2));

try {
const formData = JSON.parse(event.body);

const refererError = RequestValidator.validateReferer(event.headers, process.env['ALLOWED_REFERER']);
if(!refererError) config = await configManager.loadConfig(FileType.Game);
const validationError = refererError === "" ? await RequestValidator.validateRequest(formData, FileType.Game, config.recaptchaV2VerifyUrl, config.recaptchaV2SecretKey) : 'Access forbidden';
if (validationError) {
return { statusCode: validationError === 'Access forbidden' ? 403 : 400, headers: HEADERS, body: validationError };
}

const pullRequestManager = new PullRequestManager();

await pullRequestManager.createPullRequest(
formData,
{
githubAppId: config.githubAppId,
githubAppInstallationId: config.githubAppInstallationId,
githubAppPkBase64: config.githubAppPkBase64,
githubOwner: config.githubOwner,
githubRepo: config.githubRepo,
githubBaseBranch: config.githubBaseBranch
},
FileType.Game
);
return { statusCode: 200, headers: HEADERS, body: 'Successfully processed request' };
} catch (error) {
return handleError(error);
}
};

const handleError = (error) => {
console.error(error);
return { statusCode: 500, headers: HEADERS, body: "An error occurred while processing your form submission." };
};
4 changes: 4 additions & 0 deletions aws-lambdas/models/file-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const FileType = Object.freeze({
Application: "Application",
Game: "Game"
});
33 changes: 33 additions & 0 deletions aws-lambdas/models/pull-request-template-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export const pullRequestTemplateData = {
applicationFileContentTemplate: template`---\nname: ${0}\ncategories: [${1}]\ncompatibility: ${2}\ndisplay_result: ${3}\nversion_from: ${4}\nlink: ${5}\nicon: ${6}\n---`,
applicationFilePathTemplate: template`src/content/applications/${0}.md`,
commitMessageTemplate: template`Add ${0}.md file`,
gameFileContentTemplate: template`---\nname: ${0}\ncategories: [${1}]\npublisher: ${2}\ncompatibility: ${3}\ncompatibility_details: ${4}\ndevice_configuration: ${5}\ndate_tested: ${6}\nos_version: ${7}\ndriver_id: ${8}${9}\n---`,
gameFileSubContentTemplate: template`\nauto_super_resolution:\n\tcompatibility: ${0}\n\tfps boost: ${1}`,
gameFilePathTemplate: template`src/content/games/${0}.md`,
headBaseBranchTemplate: template`heads/${0}`,
newBranchNameTemplate: template`feat/add-${0}-${1}-file-${2}`, // ex: feat/add-outlook-application-file-1234
newBranchRefNameTemplate: template`refs/heads/${0}`,
pullRequestBodyTemplate: template`Incoming changes:\n- Add file for ${0} ${1} from ${2}\n- This data corresponds to a new ${1}\n\n**NOTE**: this Pull Request has been automatically generated.`,
pullRequestTitleTemplate: template`[Feature] Add file for ${0} ${1}`, // ex: [Feature] Add file for Outlook Application
};

export const issueTemplateData = {
title: template`[Request] Test ${0} application`,
body: template`This issue corresponds to a request to test the **${0}** application from **${1}**.\nPlease proceed with the testing process at your earliest convenience.\n**NOTE**: this Issue has been automatically generated.`
}

// Tagged template function for creating templates with placeholders
// Usage: const value = template`list of values: ${0}, ${1}, {2}`;
// value("first", "second", "third"); // outputs: "list of values: first, second, third";
function template(strings, ...keys) {
return (...values) => {
const dict = values[values.length - 1] || {};
const result = [strings[0]];
keys.forEach((key, i) => {
const value = Number.isInteger(key) ? values[key] : dict[key];
result.push(value, strings[i + 1]);
});
return result.join("");
};
}
Loading

0 comments on commit 4a0bfdf

Please sign in to comment.