Install and use prettier
This commit is contained in:
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
1
.prettierrc.json
Normal file
1
.prettierrc.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
14
README.md
14
README.md
@@ -1,5 +1,4 @@
|
|||||||
[](https://www.serverless-fullstack-app.com)
|
||||||
)](https://www.serverless-fullstack-app.com)
|
|
||||||
|
|
||||||
A complete, serverless, full-stack application built on AWS Lambda, AWS HTTP API, Express.js, React and DynamoDB.
|
A complete, serverless, full-stack application built on AWS Lambda, AWS HTTP API, Express.js, React and DynamoDB.
|
||||||
|
|
||||||
@@ -9,7 +8,6 @@ A complete, serverless, full-stack application built on AWS Lambda, AWS HTTP API
|
|||||||
|
|
||||||
Install the latest version of the Serverless Framework:
|
Install the latest version of the Serverless Framework:
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
npm i -g serverless
|
npm i -g serverless
|
||||||
```
|
```
|
||||||
@@ -99,11 +97,11 @@ Enjoy! This is a work in progress and we will continue to add funcitonality to
|
|||||||
|
|
||||||
For more details on each part of this fullstack application, check out these resources:
|
For more details on each part of this fullstack application, check out these resources:
|
||||||
|
|
||||||
* [Serverless Components](https://github.com/serverless/components)
|
- [Serverless Components](https://github.com/serverless/components)
|
||||||
* [Serverless Express](https://github.com/serverless-components/express)
|
- [Serverless Express](https://github.com/serverless-components/express)
|
||||||
* [Serverless Website](https://github.com/serverless-components/website)
|
- [Serverless Website](https://github.com/serverless-components/website)
|
||||||
* [Serverless AWS DynamoDB](https://github.com/serverless-components/aws-dynamodb)
|
- [Serverless AWS DynamoDB](https://github.com/serverless-components/aws-dynamodb)
|
||||||
* [Serverless AWS IAM Role](https://github.com/serverless-components/aws-iam-role)
|
- [Serverless AWS IAM Role](https://github.com/serverless-components/aws-iam-role)
|
||||||
|
|
||||||
## Guides
|
## Guides
|
||||||
|
|
||||||
|
|||||||
73
api/app.js
73
api/app.js
@@ -1,16 +1,17 @@
|
|||||||
const express = require('express')
|
const express = require("express");
|
||||||
const app = express()
|
const app = express();
|
||||||
const passport = require('passport')
|
const passport = require("passport");
|
||||||
const {
|
const { users } = require("./controllers");
|
||||||
users
|
|
||||||
} = require('./controllers')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure Passport
|
* Configure Passport
|
||||||
*/
|
*/
|
||||||
|
|
||||||
try { require('./config/passport')(passport) }
|
try {
|
||||||
catch (error) { console.log(error) }
|
require("./config/passport")(passport);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure Express.js Middleware
|
* Configure Express.js Middleware
|
||||||
@@ -18,26 +19,24 @@ catch (error) { console.log(error) }
|
|||||||
|
|
||||||
// Enable CORS
|
// Enable CORS
|
||||||
app.use(function (req, res, next) {
|
app.use(function (req, res, next) {
|
||||||
res.header('Access-Control-Allow-Origin', '*')
|
res.header("Access-Control-Allow-Origin", "*");
|
||||||
res.header('Access-Control-Allow-Methods', '*')
|
res.header("Access-Control-Allow-Methods", "*");
|
||||||
res.header('Access-Control-Allow-Headers', '*')
|
res.header("Access-Control-Allow-Headers", "*");
|
||||||
res.header('x-powered-by', 'serverless-express')
|
res.header("x-powered-by", "serverless-express");
|
||||||
next()
|
next();
|
||||||
})
|
});
|
||||||
|
|
||||||
// Initialize Passport and restore authentication state, if any, from the session
|
// Initialize Passport and restore authentication state, if any, from the session
|
||||||
app.use(passport.initialize())
|
app.use(passport.initialize());
|
||||||
app.use(passport.session())
|
app.use(passport.session());
|
||||||
|
|
||||||
// Enable JSON use
|
// Enable JSON use
|
||||||
app.use(express.json())
|
app.use(express.json());
|
||||||
|
|
||||||
// Since Express doesn't support error handling of promises out of the box,
|
// Since Express doesn't support error handling of promises out of the box,
|
||||||
// this handler enables that
|
// this handler enables that
|
||||||
const asyncHandler = fn => (req, res, next) => {
|
const asyncHandler = (fn) => (req, res, next) => {
|
||||||
return Promise
|
return Promise.resolve(fn(req, res, next)).catch(next);
|
||||||
.resolve(fn(req, res, next))
|
|
||||||
.catch(next);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,37 +44,43 @@ const asyncHandler = fn => (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
app.options(`*`, (req, res) => {
|
app.options(`*`, (req, res) => {
|
||||||
res.status(200).send()
|
res.status(200).send();
|
||||||
})
|
});
|
||||||
|
|
||||||
app.post(`/users/register`, asyncHandler(users.register))
|
app.post(`/users/register`, asyncHandler(users.register));
|
||||||
|
|
||||||
app.post(`/users/login`, asyncHandler(users.login))
|
app.post(`/users/login`, asyncHandler(users.login));
|
||||||
|
|
||||||
app.get(`/test/`, (req, res) => {
|
app.get(`/test/`, (req, res) => {
|
||||||
res.status(200).send('Request received')
|
res.status(200).send("Request received");
|
||||||
})
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Routes - Protected
|
* Routes - Protected
|
||||||
*/
|
*/
|
||||||
|
|
||||||
app.post(`/user`, passport.authenticate('jwt', { session: false }), asyncHandler(users.get))
|
app.post(
|
||||||
|
`/user`,
|
||||||
|
passport.authenticate("jwt", { session: false }),
|
||||||
|
asyncHandler(users.get)
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Routes - Catch-All
|
* Routes - Catch-All
|
||||||
*/
|
*/
|
||||||
|
|
||||||
app.get(`/*`, (req, res) => {
|
app.get(`/*`, (req, res) => {
|
||||||
res.status(404).send('Route not found')
|
res.status(404).send("Route not found");
|
||||||
})
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error Handler
|
* Error Handler
|
||||||
*/
|
*/
|
||||||
app.use(function (err, req, res, next) {
|
app.use(function (err, req, res, next) {
|
||||||
console.error(err)
|
console.error(err);
|
||||||
res.status(500).json({ error: `Internal Serverless Error - "${err.message}"` })
|
res
|
||||||
})
|
.status(500)
|
||||||
|
.json({ error: `Internal Serverless Error - "${err.message}"` });
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = app
|
module.exports = app;
|
||||||
|
|||||||
@@ -2,26 +2,30 @@
|
|||||||
* Config: Passport.js
|
* Config: Passport.js
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const StrategyJWT = require('passport-jwt').Strategy
|
const StrategyJWT = require("passport-jwt").Strategy;
|
||||||
const ExtractJWT = require('passport-jwt').ExtractJwt
|
const ExtractJWT = require("passport-jwt").ExtractJwt;
|
||||||
const { users } = require('../models')
|
const { users } = require("../models");
|
||||||
const { comparePassword } = require('../utils')
|
const { comparePassword } = require("../utils");
|
||||||
|
|
||||||
module.exports = (passport) => {
|
module.exports = (passport) => {
|
||||||
|
const options = {};
|
||||||
|
options.jwtFromRequest = ExtractJWT.fromAuthHeaderAsBearerToken();
|
||||||
|
options.secretOrKey = process.env.tokenSecret; // Change this to only use your own secret token
|
||||||
|
|
||||||
const options = {}
|
passport.use(
|
||||||
options.jwtFromRequest = ExtractJWT.fromAuthHeaderAsBearerToken()
|
new StrategyJWT(options, async (jwtPayload, done) => {
|
||||||
options.secretOrKey = process.env.tokenSecret || 'secret_j91jasf0j1asfkl' // Change this to only use your own secret token
|
let user;
|
||||||
|
try {
|
||||||
passport.use(new StrategyJWT(options, async (jwtPayload, done) => {
|
user = await users.getById(jwtPayload.id);
|
||||||
let user
|
} catch (error) {
|
||||||
try { user = await users.getById(jwtPayload.id) }
|
console.log(error);
|
||||||
catch (error) {
|
return done(error, null);
|
||||||
console.log(error)
|
|
||||||
return done(error, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) { return done(null, false) }
|
if (!user) {
|
||||||
return done(null, user)
|
return done(null, false);
|
||||||
}))
|
}
|
||||||
}
|
return done(null, user);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const users = require('./users')
|
const users = require("./users");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
users,
|
users,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* Controllers: Users
|
* Controllers: Users
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const jwt = require('jsonwebtoken')
|
const jwt = require("jsonwebtoken");
|
||||||
const { users } = require('../models')
|
const { users } = require("../models");
|
||||||
const { comparePassword } = require('../utils')
|
const { comparePassword } = require("../utils");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save
|
* Save
|
||||||
@@ -13,27 +13,26 @@ const { comparePassword } = require('../utils')
|
|||||||
* @param {*} next
|
* @param {*} next
|
||||||
*/
|
*/
|
||||||
const register = async (req, res, next) => {
|
const register = async (req, res, next) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await users.register(req.body)
|
await users.register(req.body);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return res.status(400).json({ error: error.message })
|
return res.status(400).json({ error: error.message });
|
||||||
}
|
}
|
||||||
|
|
||||||
let user
|
let user;
|
||||||
try {
|
try {
|
||||||
user = await users.getByEmail(req.body.email)
|
user = await users.getByEmail(req.body.email);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
return next(error, null)
|
return next(error, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = jwt.sign(user, process.env.tokenSecret, {
|
const token = jwt.sign(user, process.env.tokenSecret, {
|
||||||
expiresIn: 604800 // 1 week
|
expiresIn: 604800, // 1 week
|
||||||
})
|
});
|
||||||
|
|
||||||
res.json({ message: 'Authentication successful', token })
|
res.json({ message: "Authentication successful", token });
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign a user in
|
* Sign a user in
|
||||||
@@ -42,26 +41,32 @@ const register = async (req, res, next) => {
|
|||||||
* @param {*} next
|
* @param {*} next
|
||||||
*/
|
*/
|
||||||
const login = async (req, res, next) => {
|
const login = async (req, res, next) => {
|
||||||
|
let user;
|
||||||
let user
|
try {
|
||||||
try { user = await users.getByEmail(req.body.email) }
|
user = await users.getByEmail(req.body.email);
|
||||||
catch (error) { return done(error, null) }
|
} catch (error) {
|
||||||
|
return done(error, null);
|
||||||
if (!user) {
|
|
||||||
return res.status(404).send({ error: 'Authentication failed. User not found.' })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isCorrect = comparePassword(req.body.password, user.password)
|
if (!user) {
|
||||||
|
return res
|
||||||
|
.status(404)
|
||||||
|
.send({ error: "Authentication failed. User not found." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCorrect = comparePassword(req.body.password, user.password);
|
||||||
if (!isCorrect) {
|
if (!isCorrect) {
|
||||||
return res.status(401).send({ error: 'Authentication failed. Wrong password.' })
|
return res
|
||||||
|
.status(401)
|
||||||
|
.send({ error: "Authentication failed. Wrong password." });
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = jwt.sign(user, process.env.tokenSecret, {
|
const token = jwt.sign(user, process.env.tokenSecret, {
|
||||||
expiresIn: 604800 // 1 week
|
expiresIn: 604800, // 1 week
|
||||||
})
|
});
|
||||||
|
|
||||||
res.json({ message: 'Authentication successful', token })
|
res.json({ message: "Authentication successful", token });
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a user
|
* Get a user
|
||||||
@@ -70,12 +75,12 @@ const login = async (req, res, next) => {
|
|||||||
* @param {*} next
|
* @param {*} next
|
||||||
*/
|
*/
|
||||||
const get = async (req, res, next) => {
|
const get = async (req, res, next) => {
|
||||||
const user = users.convertToPublicFormat(req.user)
|
const user = users.convertToPublicFormat(req.user);
|
||||||
res.json({ user })
|
res.json({ user });
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
register,
|
register,
|
||||||
login,
|
login,
|
||||||
get,
|
get,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const users = require('./users')
|
const users = require("./users");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
users,
|
users,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -2,116 +2,113 @@
|
|||||||
* Model: Users
|
* Model: Users
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const AWS = require('aws-sdk')
|
const AWS = require("aws-sdk");
|
||||||
const shortid = require('shortid')
|
const shortid = require("shortid");
|
||||||
const utils = require('../utils')
|
const utils = require("../utils");
|
||||||
|
|
||||||
const dynamodb = new AWS.DynamoDB.DocumentClient({
|
const dynamodb = new AWS.DynamoDB.DocumentClient({
|
||||||
region: process.env.AWS_REGION
|
region: process.env.AWS_REGION,
|
||||||
})
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register user
|
* Register user
|
||||||
* @param {string} user.email User email
|
* @param {string} user.email User email
|
||||||
* @param {string} user.password User password
|
* @param {string} user.password User password
|
||||||
*/
|
*/
|
||||||
const register = async(user = {}) => {
|
const register = async (user = {}) => {
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
if (!user.email) {
|
if (!user.email) {
|
||||||
throw new Error(`"email" is required`)
|
throw new Error(`"email" is required`);
|
||||||
}
|
}
|
||||||
if (!user.password) {
|
if (!user.password) {
|
||||||
throw new Error(`"password" is required`)
|
throw new Error(`"password" is required`);
|
||||||
}
|
}
|
||||||
if (!utils.validateEmailAddress(user.email)) {
|
if (!utils.validateEmailAddress(user.email)) {
|
||||||
throw new Error(`"${user.email}" is not a valid email address`)
|
throw new Error(`"${user.email}" is not a valid email address`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user is already registered
|
// Check if user is already registered
|
||||||
const existingUser = await getByEmail(user.email)
|
const existingUser = await getByEmail(user.email);
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
throw new Error(`A user with email "${user.email}" is already registered`)
|
throw new Error(`A user with email "${user.email}" is already registered`);
|
||||||
}
|
}
|
||||||
|
|
||||||
user.password = utils.hashPassword(user.password)
|
user.password = utils.hashPassword(user.password);
|
||||||
|
|
||||||
// Save
|
// Save
|
||||||
const params = {
|
const params = {
|
||||||
TableName: process.env.db,
|
TableName: process.env.db,
|
||||||
Item: {
|
Item: {
|
||||||
hk: user.email,
|
hk: user.email,
|
||||||
sk: 'user',
|
sk: "user",
|
||||||
sk2: shortid.generate(),
|
sk2: shortid.generate(),
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
password: user.password,
|
password: user.password,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
await dynamodb.put(params).promise()
|
await dynamodb.put(params).promise();
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get user by email address
|
* Get user by email address
|
||||||
* @param {string} email
|
* @param {string} email
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const getByEmail = async(email) => {
|
const getByEmail = async (email) => {
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
if (!email) {
|
if (!email) {
|
||||||
throw new Error(`"email" is required`)
|
throw new Error(`"email" is required`);
|
||||||
}
|
}
|
||||||
if (!utils.validateEmailAddress(email)) {
|
if (!utils.validateEmailAddress(email)) {
|
||||||
throw new Error(`"${email}" is not a valid email address`)
|
throw new Error(`"${email}" is not a valid email address`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query
|
// Query
|
||||||
const params = {
|
const params = {
|
||||||
TableName: process.env.db,
|
TableName: process.env.db,
|
||||||
KeyConditionExpression: 'hk = :hk',
|
KeyConditionExpression: "hk = :hk",
|
||||||
ExpressionAttributeValues: { ':hk': email }
|
ExpressionAttributeValues: { ":hk": email },
|
||||||
}
|
};
|
||||||
|
|
||||||
let user = await dynamodb.query(params).promise()
|
let user = await dynamodb.query(params).promise();
|
||||||
|
|
||||||
user = user.Items && user.Items[0] ? user.Items[0] : null
|
user = user.Items && user.Items[0] ? user.Items[0] : null;
|
||||||
if (user) {
|
if (user) {
|
||||||
user.id = user.sk2
|
user.id = user.sk2;
|
||||||
user.email = user.hk
|
user.email = user.hk;
|
||||||
}
|
}
|
||||||
return user
|
return user;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get user by id
|
* Get user by id
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const getById = async(id) => {
|
const getById = async (id) => {
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new Error(`"id" is required`)
|
throw new Error(`"id" is required`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query
|
// Query
|
||||||
const params = {
|
const params = {
|
||||||
TableName: process.env.db,
|
TableName: process.env.db,
|
||||||
IndexName: process.env.dbIndex1,
|
IndexName: process.env.dbIndex1,
|
||||||
KeyConditionExpression: 'sk2 = :sk2 and sk = :sk',
|
KeyConditionExpression: "sk2 = :sk2 and sk = :sk",
|
||||||
ExpressionAttributeValues: { ':sk2': id, ':sk': 'user' }
|
ExpressionAttributeValues: { ":sk2": id, ":sk": "user" },
|
||||||
}
|
};
|
||||||
let user = await dynamodb.query(params).promise()
|
let user = await dynamodb.query(params).promise();
|
||||||
|
|
||||||
user = user.Items && user.Items[0] ? user.Items[0] : null
|
user = user.Items && user.Items[0] ? user.Items[0] : null;
|
||||||
if (user) {
|
if (user) {
|
||||||
user.id = user.sk2
|
user.id = user.sk2;
|
||||||
user.email = user.hk
|
user.email = user.hk;
|
||||||
}
|
}
|
||||||
return user
|
return user;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert user record to public format
|
* Convert user record to public format
|
||||||
@@ -119,18 +116,18 @@ const getById = async(id) => {
|
|||||||
* @param {*} user
|
* @param {*} user
|
||||||
*/
|
*/
|
||||||
const convertToPublicFormat = (user = {}) => {
|
const convertToPublicFormat = (user = {}) => {
|
||||||
user.email = user.hk || null
|
user.email = user.hk || null;
|
||||||
user.id = user.sk2 || null
|
user.id = user.sk2 || null;
|
||||||
if (user.hk) delete user.hk
|
if (user.hk) delete user.hk;
|
||||||
if (user.sk) delete user.sk
|
if (user.sk) delete user.sk;
|
||||||
if (user.sk2) delete user.sk2
|
if (user.sk2) delete user.sk2;
|
||||||
if (user.password) delete user.password
|
if (user.password) delete user.password;
|
||||||
return user
|
return user;
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
register,
|
register,
|
||||||
getByEmail,
|
getByEmail,
|
||||||
getById,
|
getById,
|
||||||
convertToPublicFormat,
|
convertToPublicFormat,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "serverless-fullstack-app-api",
|
"name": "forgetmenot",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
@@ -15,6 +14,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "Ben Ramey <benramey@fastmail.com>",
|
||||||
"license": "ISC"
|
"license": "ISC",
|
||||||
|
"description": ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,34 +2,35 @@
|
|||||||
* Utils
|
* Utils
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const bcrypt = require('bcryptjs')
|
const bcrypt = require("bcryptjs");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate email address
|
* Validate email address
|
||||||
*/
|
*/
|
||||||
const validateEmailAddress = (email) => {
|
const validateEmailAddress = (email) => {
|
||||||
var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
var re =
|
||||||
return re.test(String(email).toLowerCase())
|
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
}
|
return re.test(String(email).toLowerCase());
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash password
|
* Hash password
|
||||||
* @param {*} user
|
* @param {*} user
|
||||||
*/
|
*/
|
||||||
const hashPassword = (password) => {
|
const hashPassword = (password) => {
|
||||||
const salt = bcrypt.genSaltSync(10)
|
const salt = bcrypt.genSaltSync(10);
|
||||||
return bcrypt.hashSync(password, salt)
|
return bcrypt.hashSync(password, salt);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare password
|
* Compare password
|
||||||
*/
|
*/
|
||||||
const comparePassword = (candidatePassword, trustedPassword) => {
|
const comparePassword = (candidatePassword, trustedPassword) => {
|
||||||
return bcrypt.compareSync(candidatePassword, trustedPassword)
|
return bcrypt.compareSync(candidatePassword, trustedPassword);
|
||||||
}
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
hashPassword,
|
hashPassword,
|
||||||
comparePassword,
|
comparePassword,
|
||||||
validateEmailAddress
|
validateEmailAddress,
|
||||||
}
|
};
|
||||||
|
|||||||
18
package.json
Normal file
18
package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "forgetmenot-serverless",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "[](https://www.serverless-fullstack-app.com)",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://grimfere@dev.azure.com/grimfere/ForgetMeNot/_git/forgetmenot-serverless"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "2.4.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ Any static assets, like images, can be placed in the `public/` directory.
|
|||||||
All commands are run from the root of the project, from a terminal:
|
All commands are run from the root of the project, from a terminal:
|
||||||
|
|
||||||
| Command | Action |
|
| Command | Action |
|
||||||
|:----------------|:--------------------------------------------|
|
| :-------------- | :------------------------------------------ |
|
||||||
| `npm install` | Installs dependencies |
|
| `npm install` | Installs dependencies |
|
||||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||||
| `npm run build` | Build your production site to `./dist/` |
|
| `npm run build` | Build your production site to `./dist/` |
|
||||||
|
|||||||
@@ -12,7 +12,5 @@ export default {
|
|||||||
// port: 3000, // The port to run the dev server on.
|
// port: 3000, // The port to run the dev server on.
|
||||||
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
|
// tailwindConfig: '', // Path to tailwind.config.js if used, e.g. './tailwind.config.js'
|
||||||
},
|
},
|
||||||
renderers: [
|
renderers: ["@astrojs/renderer-react"],
|
||||||
"@astrojs/renderer-react"
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,10 +4,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
|
||||||
|
sans-serif, Apple Color Emoji, Segoe UI Emoji;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
--user-font-scale: 1rem - 16px;
|
--user-font-scale: 1rem - 16px;
|
||||||
font-size: clamp(0.875rem, 0.4626rem + 1.0309vw + var(--user-font-scale), 1.125rem);
|
font-size: clamp(
|
||||||
|
0.875rem,
|
||||||
|
0.4626rem + 1.0309vw + var(--user-font-scale),
|
||||||
|
1.125rem
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
:root {
|
:root {
|
||||||
--font-mono: Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono',
|
--font-mono: Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console",
|
||||||
'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace;
|
"Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono",
|
||||||
|
"Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier,
|
||||||
|
monospace;
|
||||||
--color-light: #f3f4f6;
|
--color-light: #f3f4f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from "react";
|
||||||
|
|
||||||
export default function ReactCounter() {
|
export default function ReactCounter() {
|
||||||
const [count, setCount] = useState(0);
|
const [count, setCount] = useState(0);
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
* Global Config
|
* Global Config
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const config = {}
|
const config = {};
|
||||||
|
|
||||||
// Domains
|
// Domains
|
||||||
config.domains = {}
|
config.domains = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API Domain
|
* API Domain
|
||||||
@@ -13,6 +13,6 @@ config.domains = {}
|
|||||||
* This will enable your front-end to communicate with your back-end.
|
* This will enable your front-end to communicate with your back-end.
|
||||||
* (e.g. 'https://api.mydomain.com' or 'https://091jafsl10.execute-api.us-east-1.amazonaws.com')
|
* (e.g. 'https://api.mydomain.com' or 'https://091jafsl10.execute-api.us-east-1.amazonaws.com')
|
||||||
*/
|
*/
|
||||||
config.domains.api = 'https://1t8da6s66e.execute-api.us-east-1.amazonaws.com'
|
config.domains.api = "https://1t8da6s66e.execute-api.us-east-1.amazonaws.com";
|
||||||
|
|
||||||
export default config
|
export default config;
|
||||||
|
|||||||
@@ -2,70 +2,69 @@
|
|||||||
* Utils: Back-end
|
* Utils: Back-end
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import config from '../config'
|
import config from "../config";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new user
|
* Register a new user
|
||||||
*/
|
*/
|
||||||
export const userRegister = async (email, password) => {
|
export const userRegister = async (email, password) => {
|
||||||
return await requestApi('/users/register', 'POST', { email, password })
|
return await requestApi("/users/register", "POST", { email, password });
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login a new user
|
* Login a new user
|
||||||
*/
|
*/
|
||||||
export const userLogin = async (email, password) => {
|
export const userLogin = async (email, password) => {
|
||||||
return await requestApi('/users/login', 'POST', { email, password })
|
return await requestApi("/users/login", "POST", { email, password });
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* userGet
|
* userGet
|
||||||
*/
|
*/
|
||||||
export const userGet = async (token) => {
|
export const userGet = async (token) => {
|
||||||
return await requestApi('/user', 'POST', null, {
|
return await requestApi("/user", "POST", null, {
|
||||||
Authorization: `Bearer ${token}`
|
Authorization: `Bearer ${token}`,
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API request to call the backend
|
* API request to call the backend
|
||||||
*/
|
*/
|
||||||
export const requestApi = async (
|
export const requestApi = async (
|
||||||
path = '',
|
path = "",
|
||||||
method = 'GET',
|
method = "GET",
|
||||||
data = null,
|
data = null,
|
||||||
headers = {}) => {
|
headers = {}
|
||||||
|
) => {
|
||||||
// Check if API URL has been set
|
// Check if API URL has been set
|
||||||
if (!config?.domains?.api) {
|
if (!config?.domains?.api) {
|
||||||
throw new Error(`Error: Missing API Domain – Please add the API domain from your serverless Express.js back-end to this front-end application. You can do this in the "site" folder, in the "./config.js" file. Instructions are listed there and in the documentation.`)
|
throw new Error(
|
||||||
|
`Error: Missing API Domain – Please add the API domain from your serverless Express.js back-end to this front-end application. You can do this in the "site" folder, in the "./config.js" file. Instructions are listed there and in the documentation.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare URL
|
// Prepare URL
|
||||||
if (!path.startsWith('/')) {
|
if (!path.startsWith("/")) {
|
||||||
path = `/${path}`
|
path = `/${path}`;
|
||||||
}
|
}
|
||||||
const url = `${config.domains.api}${path}`
|
const url = `${config.domains.api}${path}`;
|
||||||
|
|
||||||
// Set headers
|
// Set headers
|
||||||
headers = Object.assign(
|
headers = Object.assign({ "Content-Type": "application/json" }, headers);
|
||||||
{ 'Content-Type': 'application/json' },
|
|
||||||
headers
|
|
||||||
)
|
|
||||||
|
|
||||||
// Default options are marked with *
|
// Default options are marked with *
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: method.toUpperCase(),
|
method: method.toUpperCase(),
|
||||||
mode: 'cors',
|
mode: "cors",
|
||||||
cache: 'no-cache',
|
cache: "no-cache",
|
||||||
headers,
|
headers,
|
||||||
body: data ? JSON.stringify(data) : null
|
body: data ? JSON.stringify(data) : null,
|
||||||
})
|
});
|
||||||
|
|
||||||
if (response.status < 200 || response.status >= 300) {
|
if (response.status < 200 || response.status >= 300) {
|
||||||
const error = await response.json()
|
const error = await response.json();
|
||||||
throw new Error(error.error)
|
throw new Error(error.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await response.json()
|
return await response.json();
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from './api'
|
export * from "./api";
|
||||||
|
|||||||
Reference in New Issue
Block a user