- Initial commit
This commit is contained in:
1
.yarnrc.yml
Normal file
1
.yarnrc.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
nodeLinker: node-modules
|
||||||
5
nodemon.json
Normal file
5
nodemon.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"watch": ["src"],
|
||||||
|
"exec": "yarn run serve",
|
||||||
|
"ext": "ts"
|
||||||
|
}
|
||||||
68
package.json
Normal file
68
package.json
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"name": "grow-api",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"main": "server.js",
|
||||||
|
"author": "mifi (Mike Fitzpatrick)",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"lint": "tslint --project tsconfig.json --format stylish",
|
||||||
|
"prettier:fix": "prettier-eslint --eslint-config-path ./.eslintrc.js --write '**/*.ts'",
|
||||||
|
"serve": "ts-node src/server.ts",
|
||||||
|
"start": "nodemon"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tsconfig/node16": "^1.0.3",
|
||||||
|
"@types/crypto-js": "^4.1.1",
|
||||||
|
"@types/dotenv": "^8.2.0",
|
||||||
|
"@types/http-status-codes": "^1.2.0",
|
||||||
|
"@types/jsonwebtoken": "^9.0.1",
|
||||||
|
"@types/koa": "^2.13.5",
|
||||||
|
"@types/koa-bodyparser": "^4.3.10",
|
||||||
|
"@types/koa-cookie": "^1.0.0",
|
||||||
|
"@types/koa-passport": "^4.0.3",
|
||||||
|
"@types/koa-router": "^7.4.4",
|
||||||
|
"@types/koa-session": "^5.10.6",
|
||||||
|
"@types/luxon": "^3.2.0",
|
||||||
|
"@types/mongoose": "^5.11.97",
|
||||||
|
"@types/node": "^18.14.0",
|
||||||
|
"@types/passport": "^1.0.12",
|
||||||
|
"@types/passport-facebook": "^2.1.11",
|
||||||
|
"@types/passport-fido2-webauthn": "^0.1.0",
|
||||||
|
"@types/passport-google-oauth": "^1.0.42",
|
||||||
|
"@types/passport-jwt": "^3.0.8",
|
||||||
|
"@types/passport-local": "^1.0.35",
|
||||||
|
"jest": "^29.4.2",
|
||||||
|
"nodemon": "^2.0.20",
|
||||||
|
"prettier": "^2.8.4",
|
||||||
|
"prettier-eslint": "^15.0.1",
|
||||||
|
"prettier-eslint-cli": "^7.1.0",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"tslint": "^6.1.3",
|
||||||
|
"tslint-config-airbnb": "^5.11.2",
|
||||||
|
"typescript": "^4.9.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"crypto": "^1.0.1",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
|
"grow-db": "file:../grow-db",
|
||||||
|
"http-status-codes": "^2.2.0",
|
||||||
|
"jsonwebtoken": "^9.0.0",
|
||||||
|
"koa": "^2.14.1",
|
||||||
|
"koa-bodyparser": "^4.3.0",
|
||||||
|
"koa-cookie": "^1.0.0",
|
||||||
|
"koa-passport": "^6.0.0",
|
||||||
|
"koa-router": "^12.0.0",
|
||||||
|
"koa-session": "^6.4.0",
|
||||||
|
"luxon": "^3.3.0",
|
||||||
|
"mongoose": "^6.9.2",
|
||||||
|
"passport": "^0.6.0",
|
||||||
|
"passport-facebook": "^3.0.0",
|
||||||
|
"passport-fido2-webauthn": "^0.1.0",
|
||||||
|
"passport-google-oauth": "^2.0.0",
|
||||||
|
"passport-jwt": "^4.0.1",
|
||||||
|
"passport-local": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/app/app.ts
Normal file
27
src/app/app.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import Koa from 'koa';
|
||||||
|
import bodyParser from 'koa-bodyparser';
|
||||||
|
import cookie from 'koa-cookie';
|
||||||
|
import passport from 'koa-passport';
|
||||||
|
import session from 'koa-session';
|
||||||
|
|
||||||
|
import { performanceLogger, perfromanceTimer } from '../middleware/performance';
|
||||||
|
import { errorHandler } from '../middleware/errorHandler';
|
||||||
|
|
||||||
|
const app: Koa = new Koa();
|
||||||
|
|
||||||
|
app.use(errorHandler);
|
||||||
|
app.use(perfromanceTimer);
|
||||||
|
app.use(performanceLogger);
|
||||||
|
app.use(bodyParser());
|
||||||
|
app.use(cookie());
|
||||||
|
|
||||||
|
app.keys = [process.env.SESSION_KEYS as string];
|
||||||
|
app.use(session({}, app));
|
||||||
|
|
||||||
|
app.use(passport.initialize())
|
||||||
|
app.use(passport.session())
|
||||||
|
|
||||||
|
// Application error logging.
|
||||||
|
app.on('error', console.error);
|
||||||
|
|
||||||
|
export default app;
|
||||||
16
src/auth.ts
Normal file
16
src/auth.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import passport from 'koa-passport';
|
||||||
|
|
||||||
|
import Users from 'grow-db/lib/models/users';
|
||||||
|
import { User } from 'grow-db/lib/schemas/user';
|
||||||
|
|
||||||
|
passport.serializeUser((user: User, done) => { done(null, user._id); });
|
||||||
|
|
||||||
|
passport.deserializeUser(async (id, done) => {
|
||||||
|
const user = await Users.findById(id);
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
done(null, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
done('user not found', null);
|
||||||
|
});
|
||||||
231
src/controllers/auth.ts
Normal file
231
src/controllers/auth.ts
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
// const errors = require('restify-errors');
|
||||||
|
|
||||||
|
// const config = require('../config');
|
||||||
|
|
||||||
|
// const handlePassportResponse = (req, res, next) => (err, user, info) => {
|
||||||
|
// if (err) {
|
||||||
|
// return next(err);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const isVerifiedUser = user &&
|
||||||
|
// user.isRegistrationVerified();
|
||||||
|
|
||||||
|
// if (user && isVerifiedUser) {
|
||||||
|
// return res.send({ ...user.toAuthJSON() });
|
||||||
|
// } else if (user && !isVerifiedUser){
|
||||||
|
// return res.send({
|
||||||
|
// registrationSuccess: true,
|
||||||
|
// nextSteps: 'Check your email for our confirmation email, you will not be able to login without confirming.'
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return res.send(400, info);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// module.exports = function (server, auth) {
|
||||||
|
// const { passport } = auth;
|
||||||
|
|
||||||
|
// /* Local Auth */
|
||||||
|
// server.post('/auth', (req, res, next) => {
|
||||||
|
// const { body: { username = null, password = null } = {} } = req;
|
||||||
|
|
||||||
|
// if (!username || !password) {
|
||||||
|
// let errors = {};
|
||||||
|
|
||||||
|
// if (!username) {
|
||||||
|
// errors.username = 'is required';
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!password) {
|
||||||
|
// errors.password = 'is required';
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return res.send(422, { errors });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const callback = handlePassportResponse(req, res, next);
|
||||||
|
// return passport.authenticate('local', { session: false }.then(callback)(req, res, next);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * SERVICES
|
||||||
|
// */
|
||||||
|
|
||||||
|
// /* Google */
|
||||||
|
// server.get(
|
||||||
|
// '/auth/google',
|
||||||
|
// passport.authenticate('google', { scope: 'profile email', session: false }),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// server.get(
|
||||||
|
// '/auth/google/callback',
|
||||||
|
// (req, res, next) => {
|
||||||
|
// const callback = handlePassportResponse(req, res, next);
|
||||||
|
// return passport.authenticate(
|
||||||
|
// 'google',
|
||||||
|
// { failureRedirect: '/login' },
|
||||||
|
// callback,
|
||||||
|
// )(req, res, next);
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
|
||||||
|
// /* Facebook */
|
||||||
|
// server.get(
|
||||||
|
// '/auth/facebook/login',
|
||||||
|
// passport.authenticate('facebook', {
|
||||||
|
// scope: ['email', 'public_profile'],
|
||||||
|
// session: false,
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// server.get(
|
||||||
|
// '/auth/facebook/loggedin',
|
||||||
|
// (req, res, next) => {
|
||||||
|
// const callback = handlePassportResponse(req, res, next);
|
||||||
|
// return passport.authenticate(
|
||||||
|
// 'facebook',
|
||||||
|
// { failureRedirect: '/login' },
|
||||||
|
// callback,
|
||||||
|
// )(req, res, next);
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
|
||||||
|
// server.get(
|
||||||
|
// '/auth/facebook/link',
|
||||||
|
// auth.secure,
|
||||||
|
// (req, res, next) => {
|
||||||
|
// req.user.record.setLinkCheckBit((err, linkCheckBit) => {
|
||||||
|
// passport.authenticate('facebookLink', {
|
||||||
|
// scope: ['email', 'public_profile'],
|
||||||
|
// session: false,
|
||||||
|
// state: linkCheckbit,
|
||||||
|
// })(req, res, next);
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// server.get(
|
||||||
|
// '/auth/facebook/linked',
|
||||||
|
// (req, res, next) => {
|
||||||
|
// const linkCheckBit = req.query.state;
|
||||||
|
//
|
||||||
|
// return passport.authenticate(
|
||||||
|
// 'facebook',
|
||||||
|
// { failureRedirect: '/profile' },
|
||||||
|
// (err, profile) => {
|
||||||
|
// if (err) {
|
||||||
|
// return next(err);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// User.linkFacebookProfile(linkCheckBit, profile, (err, user) => {
|
||||||
|
// if (err) {
|
||||||
|
// return next(err);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (!user) {
|
||||||
|
// return next(err, false, 'Linking the account to Facebook was unsuccessful, please try again.');
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// res.send({
|
||||||
|
// success: true,
|
||||||
|
// info: 'Facerbook account successfully linked',
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// )(req, res, next);
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
};
|
||||||
|
|
||||||
|
import Koa from 'koa';
|
||||||
|
import Router from 'koa-router';
|
||||||
|
import { StatusCodes } from 'http-status-codes';
|
||||||
|
|
||||||
|
import Users from 'grow-db/lib/models/users';
|
||||||
|
|
||||||
|
const handlePassportResponse = (ctx: Koa.Context) => (err, user, info) => {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isVerifiedUser = user &&
|
||||||
|
user.isRegistrationVerified();
|
||||||
|
|
||||||
|
if (user && isVerifiedUser) {
|
||||||
|
return res.send({ ...user.toAuthJSON() });
|
||||||
|
} else if (user && !isVerifiedUser){
|
||||||
|
return res.send({
|
||||||
|
registrationSuccess: true,
|
||||||
|
nextSteps: 'Check your email for our confirmation email, you will not be able to login without confirming.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.send(400, info);
|
||||||
|
};
|
||||||
|
|
||||||
|
const routerOpts: Router.IRouterOptions = {
|
||||||
|
prefix: '/auth',
|
||||||
|
};
|
||||||
|
|
||||||
|
const router: Router = new Router(routerOpts);
|
||||||
|
|
||||||
|
router.get('/', async (ctx: Koa.Context) => {
|
||||||
|
const data = await Customers.find({}).exec();
|
||||||
|
ctx.body = { data };
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
router.get('/:customer_id', async (ctx: Koa.Context) => {
|
||||||
|
const data = await Customers.findById(ctx.params.customer_id).populate('person').exec();
|
||||||
|
if (!data) {
|
||||||
|
ctx.throw(StatusCodes.NOT_FOUND);
|
||||||
|
}
|
||||||
|
ctx.body = { data };
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/:customer_id', async (ctx: Koa.Context) => {
|
||||||
|
const data = await Customers.findByIdAndDelete(ctx.params.customer_id).exec();
|
||||||
|
if (!data) {
|
||||||
|
ctx.throw(StatusCodes.NOT_FOUND);
|
||||||
|
}
|
||||||
|
ctx.body = { success: true, data };
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
router.post('/', async (ctx: Koa.Context) => {
|
||||||
|
const data = await Customers.create(ctx.body);
|
||||||
|
data.save();
|
||||||
|
ctx.body = { success: true, data };
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
router.post('/', async (ctx: Koa.Context) => {
|
||||||
|
const { body: { username = null, password = null } = {} } = ctx;
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
let errors = {};
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
errors.username = 'is required';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
errors.password = 'is required';
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.status = StatusCodes.UNPROCESSABLE_ENTITY;
|
||||||
|
ctx.throw(422, { errors });
|
||||||
|
}
|
||||||
|
|
||||||
|
const callback = handlePassportResponse(req, res, next);
|
||||||
|
return passport.authenticate('local', { session: false }, callback)(req, res, next);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
router.patch('/:customer_id', async (ctx: Koa.Context) => {
|
||||||
|
const data = await Customers.findByIdAndUpdate(ctx.params.customer_id);
|
||||||
|
if (!data) {
|
||||||
|
ctx.throw(StatusCodes.NOT_FOUND);
|
||||||
|
}
|
||||||
|
ctx.body = { success: true, data };
|
||||||
|
});
|
||||||
47
src/controllers/customers.ts
Normal file
47
src/controllers/customers.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import Koa from 'koa';
|
||||||
|
import Router from 'koa-router';
|
||||||
|
import { StatusCodes } from 'http-status-codes';
|
||||||
|
|
||||||
|
import Customers from 'grow-db/lib/models/customers';
|
||||||
|
|
||||||
|
const routerOpts: Router.IRouterOptions = {
|
||||||
|
prefix: '/customers',
|
||||||
|
};
|
||||||
|
|
||||||
|
const router: Router = new Router(routerOpts);
|
||||||
|
|
||||||
|
router.get('/', async (ctx: Koa.Context) => {
|
||||||
|
const data = await Customers.find({}).exec();
|
||||||
|
ctx.body = { data };
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/:customer_id', async (ctx: Koa.Context) => {
|
||||||
|
const data = await Customers.findById(ctx.params.customer_id).populate('person').exec();
|
||||||
|
if (!data) {
|
||||||
|
ctx.throw(StatusCodes.NOT_FOUND);
|
||||||
|
}
|
||||||
|
ctx.body = { data };
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/:customer_id', async (ctx: Koa.Context) => {
|
||||||
|
const data = await Customers.findByIdAndDelete(ctx.params.customer_id).exec();
|
||||||
|
if (!data) {
|
||||||
|
ctx.throw(StatusCodes.NOT_FOUND);
|
||||||
|
}
|
||||||
|
ctx.body = { success: true, data };
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
router.post('/', async (ctx: Koa.Context) => {
|
||||||
|
const data = await Customers.create(ctx.body);
|
||||||
|
data.save();
|
||||||
|
ctx.body = { success: true, data };
|
||||||
|
});
|
||||||
|
|
||||||
|
router.patch('/:customer_id', async (ctx: Koa.Context) => {
|
||||||
|
const data = await Customers.findByIdAndUpdate(ctx.params.customer_id);
|
||||||
|
if (!data) {
|
||||||
|
ctx.throw(StatusCodes.NOT_FOUND);
|
||||||
|
}
|
||||||
|
ctx.body = { success: true, data };
|
||||||
|
});
|
||||||
17
src/controllers/login.ts
Normal file
17
src/controllers/login.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import Koa from 'koa';
|
||||||
|
import Router from 'koa-router';
|
||||||
|
import { StatusCodes } from 'http-status-codes';
|
||||||
|
|
||||||
|
import Users from 'grow-db/lib/models/users';
|
||||||
|
|
||||||
|
const routerOpts: Router.IRouterOptions = {
|
||||||
|
prefix: '/login',
|
||||||
|
};
|
||||||
|
|
||||||
|
const router: Router = new Router(routerOpts);
|
||||||
|
|
||||||
|
router.post('/', async (ctx: Koa.Context) => {
|
||||||
|
const data = await Users.create(ctx.body);
|
||||||
|
data.save();
|
||||||
|
ctx.body = { success: true, data };
|
||||||
|
});
|
||||||
46
src/controllers/people.ts
Normal file
46
src/controllers/people.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import Koa from 'koa';
|
||||||
|
import Router from 'koa-router';
|
||||||
|
import { StatusCodes } from 'http-status-codes';
|
||||||
|
|
||||||
|
import People from 'grow-db/lib/models/people';
|
||||||
|
|
||||||
|
const routerOpts: Router.IRouterOptions = {
|
||||||
|
prefix: '/people',
|
||||||
|
};
|
||||||
|
|
||||||
|
const router: Router = new Router(routerOpts);
|
||||||
|
|
||||||
|
router.get('/', async (ctx: Koa.Context) => {
|
||||||
|
const data = await People.find({}).exec();
|
||||||
|
ctx.body = { data };
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/:person_id', async (ctx: Koa.Context) => {
|
||||||
|
const data = await People.findById(ctx.params.person_id).populate('person').exec();
|
||||||
|
if (!data) {
|
||||||
|
ctx.throw(StatusCodes.NOT_FOUND);
|
||||||
|
}
|
||||||
|
ctx.body = { data };
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/:person_id', async (ctx: Koa.Context) => {
|
||||||
|
const data = await People.findByIdAndDelete(ctx.params.person_id).exec();
|
||||||
|
if (!data) {
|
||||||
|
ctx.throw(StatusCodes.NOT_FOUND);
|
||||||
|
}
|
||||||
|
ctx.body = { success: true, data };
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/', async (ctx: Koa.Context) => {
|
||||||
|
const data = await People.create(ctx.body);
|
||||||
|
data.save();
|
||||||
|
ctx.body = { success: true, data };
|
||||||
|
});
|
||||||
|
|
||||||
|
router.patch('/:person_id', async (ctx: Koa.Context) => {
|
||||||
|
const data = await People.findByIdAndUpdate(ctx.params.person_id);
|
||||||
|
if (!data) {
|
||||||
|
ctx.throw(StatusCodes.NOT_FOUND);
|
||||||
|
}
|
||||||
|
ctx.body = { success: true, data };
|
||||||
|
});
|
||||||
68
src/controllers/reset.js
Normal file
68
src/controllers/reset.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
const errors = require('restify-errors');
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
|
||||||
|
const config = require('../config');
|
||||||
|
const User = require('../models/user');
|
||||||
|
|
||||||
|
const {
|
||||||
|
api: { url },
|
||||||
|
security: { reset: { route, tokenPlaceholder } },
|
||||||
|
} = config;
|
||||||
|
|
||||||
|
const routes = {
|
||||||
|
resetWithToken: `${route}/${tokenPlaceholder}([A-Za-z0-9_]+\.{3})`,
|
||||||
|
getTestToken: `${route}/generate`,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = function (server, auth) {
|
||||||
|
server.get(routes.getTestToken, auth.secure, function (req, res, next) {
|
||||||
|
const { record: user } = req.user;
|
||||||
|
const resetToken = user.generateResetToken();
|
||||||
|
const resetUrl = `${url}${route}/${resetToken}`;
|
||||||
|
|
||||||
|
res.send({ resetToken, resetUrl });
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.post(routes.resetWithToken, auth.bypass, function (req, res, next) {
|
||||||
|
const { reset_token } = req.params;
|
||||||
|
const { password } = req.body;
|
||||||
|
|
||||||
|
if (!reset_token) {
|
||||||
|
return next(
|
||||||
|
new errors.InvalidContentError('A reset token was not provided.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
return next(
|
||||||
|
new errors.InvalidContentError('Password cannot be empty.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
User.verifyTokenAndResetPassword(reset_token, password, (err, user, info) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
return next(
|
||||||
|
new errors.InvalidContentError(err),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
console.error(err);
|
||||||
|
res.send({
|
||||||
|
success: false,
|
||||||
|
info: 'Password reset failed. ' + info,
|
||||||
|
});
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
success: true,
|
||||||
|
info: 'Password reset successful.',
|
||||||
|
...user.toAuthJSON()
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
106
src/controllers/signup.js
Normal file
106
src/controllers/signup.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
const errors = require('restify-errors');
|
||||||
|
|
||||||
|
const User = require('../models/user');
|
||||||
|
|
||||||
|
module.exports = function (server, auth) {
|
||||||
|
const { passport } = auth;
|
||||||
|
|
||||||
|
server.post('/signup', auth.basic, (req, res, next) => {
|
||||||
|
const { body: { user = null } = {} } = req;
|
||||||
|
|
||||||
|
let errors = {};
|
||||||
|
let errorCount = 0;
|
||||||
|
if (!user) {
|
||||||
|
errors.user = 'is required - can\'t make something from nothing...';
|
||||||
|
errorCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorCount) {
|
||||||
|
return res.send(422, { errors });
|
||||||
|
}
|
||||||
|
|
||||||
|
User.register(user, (err, user, info) => {
|
||||||
|
if (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info) {
|
||||||
|
res.send(200, {
|
||||||
|
success: false,
|
||||||
|
nextSteps: 'Please fix the problems indicated and try again.',
|
||||||
|
...info
|
||||||
|
});
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send(200, {
|
||||||
|
success: true,
|
||||||
|
nextSteps: 'Check your email for our confirmation email, you will not be able to login without confirming.',
|
||||||
|
});
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.get('/signup/validate/email/:email', auth.basic, (req, res, next) => {
|
||||||
|
const email = decodeURI(req.params.email);
|
||||||
|
|
||||||
|
User.findOne({ email }, (err, user) => {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send(200, { available: !!!user });
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.get('/signup/validate/nom/:nom_de_bid', auth.basic, (req, res, next) => {
|
||||||
|
const nomDeBid = decodeURI(req.params.nom_de_bid);
|
||||||
|
|
||||||
|
User.findOne({ nomDeBid }, (err, user) => {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send(200, { available: !!!user });
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.post('/signup/verify/resend', auth.basic, (req, res, next) => {
|
||||||
|
const { body: { email = null } = {} } = req;
|
||||||
|
|
||||||
|
User.resendVerificationEmail(email, (err, user, info) => {
|
||||||
|
if (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
res.send(200, {
|
||||||
|
success: false,
|
||||||
|
nextSteps: 'There was no user located with the email address provided. Please try again.',
|
||||||
|
});
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user && info.success) {
|
||||||
|
res.send(200, {
|
||||||
|
success: true,
|
||||||
|
nextSteps: 'Check your email for our confirmation email, you will not be able to login without confirming.',
|
||||||
|
});
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send(200, {
|
||||||
|
success: false,
|
||||||
|
nextSteps: 'There was a problem resending the verification email. Please try again later.',
|
||||||
|
});
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
49
src/controllers/strains.ts
Normal file
49
src/controllers/strains.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { Context } from 'koa';
|
||||||
|
import Router from 'koa-router';
|
||||||
|
import { } from 'koa-bodyparser';
|
||||||
|
import { StatusCodes } from 'http-status-codes';
|
||||||
|
|
||||||
|
import Strains from 'grow-db/lib/models/strains';
|
||||||
|
import { Strain } from 'grow-db/lib/schemas/strain';
|
||||||
|
|
||||||
|
const routerOpts: Router.IRouterOptions = {
|
||||||
|
prefix: '/strains',
|
||||||
|
};
|
||||||
|
|
||||||
|
const router: Router = new Router(routerOpts);
|
||||||
|
|
||||||
|
router.get('/', async (ctx: Context) => {
|
||||||
|
const data = await Strains.find({}).exec();
|
||||||
|
ctx.body = { data };
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/:strain_id', async (ctx) => {
|
||||||
|
const data = await Strains.findById(ctx.params.strain_id).populate('person').exec();
|
||||||
|
if (!data) {
|
||||||
|
ctx.throw(StatusCodes.NOT_FOUND);
|
||||||
|
}
|
||||||
|
ctx.body = { data };
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/:strain_id', async (ctx: Context) => {
|
||||||
|
const data = await Strains.findByIdAndDelete(ctx.params.strain_id).exec();
|
||||||
|
if (!data) {
|
||||||
|
ctx.throw(StatusCodes.NOT_FOUND);
|
||||||
|
}
|
||||||
|
ctx.body = { success: true, data };
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
router.post('/', async (ctx: Context) => {
|
||||||
|
const data = await Strains.create(ctx.request.body);
|
||||||
|
data.save();
|
||||||
|
ctx.body = { success: true, data };
|
||||||
|
});
|
||||||
|
|
||||||
|
router.patch('/:strain_id', async (ctx) => {
|
||||||
|
const data = await Strains.findByIdAndUpdate(ctx.params.strain_id, <Strain>ctx.request.body, { lean: true, returnDocument: 'after' });
|
||||||
|
if (!data) {
|
||||||
|
ctx.throw(StatusCodes.NOT_FOUND);
|
||||||
|
}
|
||||||
|
ctx.body = { success: true, data };
|
||||||
|
});
|
||||||
5
src/database/database.connection.ts
Normal file
5
src/database/database.connection.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
|
export const connection = mongoose.connect(
|
||||||
|
`${process.env.DB_USER}:${process.env.DB_PASSWORD}@mongodb:27017/${process.env.DB_NAME}`
|
||||||
|
);
|
||||||
0
src/middleware/authenication.ts
Normal file
0
src/middleware/authenication.ts
Normal file
13
src/middleware/errorHandler.ts
Normal file
13
src/middleware/errorHandler.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { StatusCodes } from "http-status-codes";
|
||||||
|
import { Context, Next } from "koa";
|
||||||
|
|
||||||
|
export const errorHandler = async (ctx: Context, next: Next) => {
|
||||||
|
try {
|
||||||
|
await next();
|
||||||
|
} catch (error: any) {
|
||||||
|
ctx.status = error.statusCode || error.status || StatusCodes.INTERNAL_SERVER_ERROR;
|
||||||
|
error.status = ctx.status;
|
||||||
|
ctx.body = { error };
|
||||||
|
ctx.app.emit('error', error, ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
15
src/middleware/performance.ts
Normal file
15
src/middleware/performance.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Next } from 'koa';
|
||||||
|
import { KoaContext } from '../types/KoaContext';
|
||||||
|
|
||||||
|
export const performanceLogger = async (ctx: KoaContext, next: Next) => {
|
||||||
|
await next();
|
||||||
|
const rt = ctx.response.get('X-Response-Time');
|
||||||
|
console.log(`${ctx.method} ${ctx.url} - ${rt}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const perfromanceTimer = async (ctx: KoaContext, next: Next) => {
|
||||||
|
const start = Date.now();
|
||||||
|
await next();
|
||||||
|
const ms = Date.now() - start;
|
||||||
|
ctx.set('X-Response-Time', `${ms}ms`);
|
||||||
|
};
|
||||||
12
src/server.ts
Normal file
12
src/server.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import dotenv from 'dotenv';
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
import app from './app/app';
|
||||||
|
import { connection } from './database/database.connection';
|
||||||
|
|
||||||
|
const PORT: number = Number(process.env.PORT) || 9000;
|
||||||
|
|
||||||
|
connection.then(
|
||||||
|
() => app.listen(PORT),
|
||||||
|
(err) => console.error('ERROR!', err),
|
||||||
|
);
|
||||||
12
src/types/KoaContext.ts
Normal file
12
src/types/KoaContext.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Context, Request } from 'koa';
|
||||||
|
|
||||||
|
interface KoaRequest<RequestBody = any> extends Request {
|
||||||
|
body?: RequestBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KoaContext<RequestBody = any, ResponseBody = any> extends Context {
|
||||||
|
request: KoaRequest<RequestBody>;
|
||||||
|
body: ResponseBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KoaResponseContext<ResponseBody> extends KoaContext<any, ResponseBody> {};
|
||||||
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/node16/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"rootDirs": ["src", "../"],
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
}
|
||||||
6
tslint.json
Normal file
6
tslint.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "tslint-config-airbnb",
|
||||||
|
"rules": {
|
||||||
|
"import-name": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user