(Unfinished) Testing build process

This commit is contained in:
2020-02-06 08:05:29 +11:00
parent 3557256d76
commit c2141e9a31
96 changed files with 6762 additions and 55 deletions

View File

@ -0,0 +1,14 @@
module.exports = {
"roots": [
"<rootDir>/src"
],
"transform": {
"^.+\\.ts?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.ts?$",
"moduleFileExtensions": [
"ts",
"js",
"json"
]
}

51
src/private/package.json Normal file
View File

@ -0,0 +1,51 @@
{
"name": "domsplace-backend",
"version": "7.0.0",
"description": "Personal website for Dominic \"YouWish\" Masters.",
"main": "./dist/private/",
"scripts": {
"build": "tsc -p .",
"start:prod": "cross-env NODE_ENV=production \"serverless offline\"",
"start:dev": "cross-env NODE_ENV=development \"serverless offline --port 3001\"",
"start": "npm run start:prod",
"watch": "npm run start:dev",
"deploy": "echo \"Building private\""
"test": "yarn jest"
},
"repository": {
"type": "git",
"url": "git+https://github.com/YourWishes/domsPlace.git"
},
"keywords": [
"domsplace",
"personal",
"portfolio",
"website"
],
"author": "Dominic Masters",
"license": "MIT",
"bugs": {
"url": "https://github.com/YourWishes/domsPlace/issues"
},
"homepage": "https://github.com/YourWishes/domsPlace#readme",
"dependencies": {
"email-validator": "^2.0.4",
"nodemailer": "^6.3.0",
"serverless-plugin-include-dependencies": "^3.2.1"
},
"devDependencies": {
"@types/jest": "^24.0.18",
"@types/node": "^12.7.1",
"@types/nodemailer": "^6.2.1",
"cross-env": "^5.2.0",
"escape-html": "^1.0.3",
"jest": "^24.9.0",
"serverless": "^1.51.0",
"serverless-finch": "^2.4.3",
"serverless-offline": "^5.11.0",
"ts-jest": "^24.1.0",
"ts-node": "^8.3.0",
"typescript": "^3.5.3",
"utility-types": "^3.7.0"
}
}

View File

@ -0,0 +1,8 @@
import { sendMail } from './../send';
describe('sendMail', () => {
it('should require a body', async () => {
await expect(sendMail({ body: null })).resolves.toHaveProperty('statusCode', 400);
});
})

View File

@ -0,0 +1,59 @@
import { withHandler } from "../../handler/handler";
import { validate } from 'email-validator';
import { createTransport } from 'nodemailer';
import * as escapeHTML from 'escape-html';
export interface sendMailParams {
name:string;
email:string;
message:string;
}
export const sendMail = withHandler<sendMailParams>(async (e,c) => {
//Required
if(!e || !e.body) return { statusCode: 400, body: 'Missing Contact Details' };
let { name, email, message } = e.body;
if(!name) return { statusCode: 400, body: 'Missing Contact Name' };
if(!email) return { statusCode: 400, body: 'Missing Contact Email' };
if(!message) return { statusCode: 400, body: 'Missing Contact Message' };
//Validate
if(name.length > 128 || !name.replace(/\s/g, '').length) return { statusCode: 400, body: 'Invalid Name' };
if(!validate(email)) return { statusCode: 400, body: 'Invalid Email' };
if(message.length > 10000 || !message.replace(/\s/g,'').length) {
return { statusCode: 400, body: 'Invalid Messatge' };
}
//Prepare mail
let {
EMAIL_HOST, EMAIL_PORT, EMAIL_USER, EMAIL_PASS, EMAIL_DEST, EMAIL_FROM
} = process.env
let transporter = createTransport({
host: EMAIL_HOST,
port: parseInt(EMAIL_PORT),
secure: true,
auth: {
user: EMAIL_USER, pass: EMAIL_PASS
}
});
let x = await transporter.sendMail({
from: `${name} <${email}>`,
to: EMAIL_DEST,
subject: 'Contact Message Received',
text: `Contact Message Received:\n${message}\nFrom: ${name} ${email}`,
html: `
<h1>Contact Message Received</h1>
<p>You have received a contact message from ${escapeHTML(name)} - ${escapeHTML(email)} who wrote:</p>
<p>
${escapeHTML(message)}
</p>
<span>Time: ${new Date().toLocaleString()}
`
});
return { statusCode: 200, body: true }
});

View File

@ -0,0 +1,7 @@
import { ping } from './../ping';
describe('ping', () => {
it('shold return a hello world and a 200 code', async () => {
await expect(ping()).resolves.toStrictEqual({ statusCode: 200, body: 'Thank funk!' });
})
})

View File

@ -0,0 +1,5 @@
import { withHandler } from "../../handler/handler";
export const ping = withHandler(async () => {
return { statusCode: 200, body: 'Thank funk!' };
});

View File

@ -0,0 +1,43 @@
import { withHandler, APICallable } from './../handler';
describe('withHandler', () => {
it('should wrap an async function into a serverless callbackable function', () => {
let callbackable = jest.fn(async () => ({ body: 'Hello World', statusCode: 200 }));
let x = withHandler(callbackable);
expect(typeof x).toBe('function');
});
it('should call the promise and pass the result to the callback', async () => {
let callbackable = async () => ({ body: 'Hello World', statusCode: 200 });
let x = withHandler(callbackable);
let fn = jest.fn();
x({} as any, null, fn);
await new Promise(resolve => setImmediate(resolve));
expect(fn).toHaveBeenCalled();
});
it('should set the content type', async () => {
let x = withHandler(async () => ({ body: 'Hello World', statusCode: 200 }));
let fn = jest.fn();
x({} as any, null, fn);
await new Promise(resolve => setImmediate(resolve));
expect(fn).toHaveBeenCalledWith(null, { body: '"Hello World"', statusCode: 200,
headers: { 'Content-Type': 'application/json'}
});
});
it('should return the invoked functions returned promise', async () => {
let x = withHandler(async () => ({ body: 'Hello World', statusCode: 200 }));
let fn = jest.fn();
let prom = x({} as any, null, fn);
let result = await prom;
expect(result).toStrictEqual({ body: 'Hello World', statusCode: 200 });
})
})

View File

@ -0,0 +1,71 @@
export type APIResponse = {
statusCode?:number;
body:any;
headers?:{
[key:string]:string,
"Content-Type"?:string
};
}
export type APICallable<T=any> = (event:APIEvent<T>, context:any) => Promise<APIResponse>;
export type APIMethod = 'GET'|'POST'|'PUT'|'PATCH'|'DELETE'|'TRACE'|'OPTIONS'|'CONNECT';
export interface APIEvent<T=any> {
body:T;
headers?:{[key:string]:string};
httpMethod?:APIMethod;
isOffline?:boolean;
multiValueHeaders?:{[key:string]:string};
multiValueQueryStringParameters?:{[key:string]:string};
path?:string;
pathParameters?:never;
queryStringParameters?:{[key:string]:string};
requestContext?:{[key:string]:string};
resource?:string;
stageVariables?:any;
}
export const withHandler = <T=any>(callable:APICallable<T>) => {
return (event?:APIEvent<T>, context?, callback?) => {
if(event && event.headers && event.headers['Content-Type']) {
let contentType = event.headers['Content-Type'];
if(contentType.indexOf('application/json') !== -1) {
try {
let body:T = JSON.parse(event.body as any);
event.body = body;
} catch(e) {
callback(null, {
statusCode: 400,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify('Invalid body')
})
}
}
}
return callable(event, context).then(d => {
if(callback) {
let contentType = (d.headers ? d.headers['Content-Type']:null) ||'application/json';
let json = contentType == 'application/json';
callback(null, {
...d,
body: json ? JSON.stringify(d.body) : d.body,
statusCode: d.statusCode || 200,
headers: {
...(d.headers||{}),
"Content-Type": contentType
}
});
}
return d;
}).catch(ex => {
if(callback) {
callback(null, { statusCode: 500, body: null, });
}
throw ex;
})
};
}

0
src/private/src/index.ts Normal file
View File

14
src/private/tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"outDir": "./dist",
"target": "es5",
"module": "commonjs",
"noImplicitAny": false,
"sourceMap": true,
"baseUrl": "./src/"
},
"include": [
"./src/**/*.ts"
],
"exclude": ["node_modules", "**/*.test.ts"]
}

6380
src/private/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
import 'normalize.css';

View File

@ -0,0 +1,42 @@
const path = require('path');
const TSConfig = require('./tsconfig.json');
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
path: path.join(__dirname, 'src', 'assets', 'images'),
name: 'images'
}
},
'gatsby-plugin-typescript',
'gatsby-plugin-react-helmet',
'gatsby-plugin-styled-components',
'gatsby-transformer-sharp',
'gatsby-plugin-sharp',
{
resolve: 'gatsby-plugin-alias-imports',
options: {
alias: Object.entries(TSConfig.compilerOptions.paths).reduce((x, [key, value]) => {
let k = key.split('/').filter(f => f && f != '*').join('/');
let v = value.find(v => v).split('/').filter(f => f && f != '*');
return { ...x, [k]: path.resolve(__dirname, TSConfig.compilerOptions.baseUrl, ...v) };
}, {})
}
},
{
resolve: 'gatsby-plugin-google-fonts',
options: {
fonts: [
'Bitter',
'Nanum Gothic'
],
display: 'swap'
}
}
]
}

62
src/public/package.json Normal file
View File

@ -0,0 +1,62 @@
{
"name": "domsplace-frontend",
"version": "8.0.0",
"description": "Personal website for Dominic \"YourWishes\" Masters.",
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"start": "npm run develop",
"serve": "gatsby serve",
"clean": "gatsby clean"
},
"repository": {
"type": "git",
"url": "git+https://github.com/YourWishes/domsPlaceNew.git"
},
"keywords": [
"domsplace",
"personal",
"portfolio",
"website"
],
"author": "Dominic Masters",
"license": "MIT",
"bugs": {
"url": "https://github.com/YourWishes/domsPlaceNew/issues"
},
"homepage": "https://domsplace.com",
"dependencies": {
"babel-plugin-styled-components": "^1.10.7",
"gatsby": "^2.18.12",
"gatsby-image": "^2.2.39",
"gatsby-plugin-alias-imports": "^1.0.5",
"gatsby-plugin-google-fonts": "^1.0.1",
"gatsby-plugin-react-helmet": "^3.1.21",
"gatsby-plugin-sharp": "^2.4.3",
"gatsby-plugin-styled-components": "^3.1.18",
"gatsby-plugin-typescript": "^2.1.26",
"gatsby-source-filesystem": "^2.1.46",
"gatsby-transformer-sharp": "^2.3.13",
"normalize.css": "^8.0.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-helmet": "^5.2.1",
"react-hook-form": "^4.8.0",
"styled-components": "^5.0.0",
"yup": "^0.28.1"
},
"devDependencies": {
"@types/node": "^13.5.0",
"@types/react": "^16.9.19",
"@types/react-dom": "^16.9.5",
"@types/styled-components": "^4.4.2",
"@types/react-helmet": "^5.0.15",
"@types/yup": "^0.26.29"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}

View File

@ -0,0 +1,12 @@
export const sendMail = (name:string, email:string, message:string) => {
return fetch('https://api.domsplace.com/v1/mail/send', {
method: 'POST',
body: JSON.stringify({
name, email, message
}),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
}
}).then(d => d.json());
}

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 377 B

After

Width:  |  Height:  |  Size: 377 B

View File

Before

Width:  |  Height:  |  Size: 790 B

After

Width:  |  Height:  |  Size: 790 B

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 770 B

After

Width:  |  Height:  |  Size: 770 B

View File

Before

Width:  |  Height:  |  Size: 672 B

After

Width:  |  Height:  |  Size: 672 B

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 335 KiB

After

Width:  |  Height:  |  Size: 335 KiB

View File

Before

Width:  |  Height:  |  Size: 502 KiB

After

Width:  |  Height:  |  Size: 502 KiB

View File

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 167 KiB

View File

Before

Width:  |  Height:  |  Size: 223 KiB

After

Width:  |  Height:  |  Size: 223 KiB

View File

@ -5,6 +5,7 @@ import { Button, ButtonGroup } from '@objects/interactive/Button';
import * as yup from 'yup';
import { Panel } from '@objects/feedback/Panel';
import { Heading2 } from '@objects/typography/Heading';
import { sendMail } from '@api/SendMail';
export interface ContactFormProps {
@ -27,7 +28,13 @@ export const ContactForm = (props:ContactFormProps) => {
const onSubmit = async (data:any) => {
setPending(true);
await new Promise(resolve => setTimeout(resolve, 3000));
//await new Promise(resolve => setTimeout(resolve, 3000));
await sendMail(
'',
'',
''
);
setPending(false);
setSuccess(true);

View File

@ -111,7 +111,7 @@ export const IconGrid = ({ icons, title, ...props }:IconGridProps) => (
<IconGridGrid delay="long" from="bottom">
<IconGridInner>
{ icons ? icons.map((icon,i) => <IconGridIcon index={i} {...icon} />) : null }
{ icons ? icons.map((icon,i) => <IconGridIcon key={i} index={i} {...icon} />) : null }
</IconGridInner>
</IconGridGrid>
</IconGridWrapper>

View File

@ -83,7 +83,7 @@ export type StackedMosaicProps = BoundaryProps & {
export const StackedMosaic = ({ title, body, images, ...p }:StackedMosaicProps) => (
<StackedMosaicWrapper {...p}>
<StackedMosaicGrid>
{images.map((e,i) => <StackedMosaicTile {...e} index={i+1} />)}
{images.map((e,i) => <StackedMosaicTile key={i} {...e} index={i+1} />)}
</StackedMosaicGrid>

36
src/public/tsconfig.json Normal file
View File

@ -0,0 +1,36 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"moduleResolution": "node",
"baseUrl": "src",
"paths": {
"@api/*": [ "api/*" ],
"@components/*": [ "components/*" ],
"@objects/*": [ "objects/*" ],
"@settings/*": [ "settings/*" ],
"@styles/*": [ "styles/*" ],
"@pages/*": [ "pages/*" ],
"@tools/*": [ "tools/*" ],
"@assets/*": [ "assets/*" ]
}
},
"include": [
"src"
]
}

10647
src/public/yarn.lock Normal file

File diff suppressed because it is too large Load Diff