user registration connects to front end
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { IUserAuth } from "../../schemas";
|
import { IUserAuth } from "../../schemas";
|
||||||
import { attemptLogin } from "../../util/apiUtils";
|
import { attemptLogin } from "../../util/apiUtils";
|
||||||
import { Button, Page, Panel } from "../ui";
|
import { Button, Page, Panel } from "../ui";
|
||||||
@@ -11,6 +11,10 @@ export default function Login() {
|
|||||||
password: ''
|
password: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const getFormState = useCallback((received: IUserAuth) => {
|
||||||
|
setInput(received);
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
if (!input.email || !input.password) return;
|
if (!input.email || !input.password) return;
|
||||||
const result = await attemptLogin(input);
|
const result = await attemptLogin(input);
|
||||||
@@ -23,9 +27,7 @@ export default function Login() {
|
|||||||
labels: ["Email", "Password"],
|
labels: ["Email", "Password"],
|
||||||
dataTypes: Object.keys(input),
|
dataTypes: Object.keys(input),
|
||||||
initialState: input,
|
initialState: input,
|
||||||
setState: setInput,
|
getState: getFormState
|
||||||
submitButtonText: "Log In",
|
|
||||||
submitFunction: handleLogin
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -38,6 +40,7 @@ export default function Login() {
|
|||||||
|
|
||||||
<Panel extraStyles="form-panel">
|
<Panel extraStyles="form-panel">
|
||||||
{ form }
|
{ form }
|
||||||
|
<Button onClick={handleLogin}>Log In</Button>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
<aside>Not registered yet? You can do that <a href="/register">here.</a></aside>
|
<aside>Not registered yet? You can do that <a href="/register">here.</a></aside>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import { IUser } from "../../../schemas";
|
import { IUser } from "../../../schemas";
|
||||||
import { attemptRegister } from "../../../util/apiUtils";
|
import { attemptRegister } from "../../../util/apiUtils";
|
||||||
import { Page, Panel } from "../../ui";
|
import { Button, Page, Panel } from "../../ui";
|
||||||
import Divider from "../../ui/Divider";
|
import Divider from "../../ui/Divider";
|
||||||
import Form, { FormConfig } from "../../ui/Form";
|
import Form, { FormConfig } from "../../ui/Form";
|
||||||
|
|
||||||
@@ -17,21 +17,34 @@ export default function AboutYou() {
|
|||||||
active: true
|
active: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getFormState = useCallback((received: IUser) => {
|
||||||
|
setInput(received);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const formConfig: FormConfig<IUser> = {
|
const formConfig: FormConfig<IUser> = {
|
||||||
parent: "register",
|
parent: "register",
|
||||||
keys: Object.keys(input),
|
keys: ['firstname', 'lastname', 'handle', 'email', 'password'],
|
||||||
initialState: input,
|
initialState: input,
|
||||||
labels: ['First Name', 'Last Name', 'Handle', 'Email', "Password", "Active?"],
|
labels: ['First Name', 'Last Name', 'Handle', 'Email', "Password"],
|
||||||
dataTypes: ['text', 'text', 'text', 'email', 'password', 'text'],
|
dataTypes: ['text', 'text', 'text', 'email', 'password'],
|
||||||
setState: setInput,
|
getState: getFormState
|
||||||
submitButtonText: 'Register',
|
|
||||||
submitFunction: () => console.log(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setForm(new Form<IUser>(formConfig).mount());
|
setForm(new Form<IUser>(formConfig).mount());
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const handleRegister = async () => {
|
||||||
|
for (let key of Object.keys(input)) {
|
||||||
|
if (!input[key as keyof IUser]) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(input);
|
||||||
|
|
||||||
|
const result = await attemptRegister(input);
|
||||||
|
console.log(result);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<h1>Hi! Thanks for being here.</h1>
|
<h1>Hi! Thanks for being here.</h1>
|
||||||
@@ -42,6 +55,7 @@ export default function AboutYou() {
|
|||||||
|
|
||||||
<Panel extraStyles="form-panel two-columns">
|
<Panel extraStyles="form-panel two-columns">
|
||||||
{ form }
|
{ form }
|
||||||
|
<Button onClick={handleRegister}>Register</Button>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ChangeEvent, Dispatch, SetStateAction, useState } from "react";
|
import { ChangeEvent } from "react";
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,11 +12,9 @@ export interface FormConfig<T> {
|
|||||||
parent: string
|
parent: string
|
||||||
keys: string[]
|
keys: string[]
|
||||||
initialState: T
|
initialState: T
|
||||||
setState: Dispatch<SetStateAction<T>>
|
getState: (received: T) => void
|
||||||
labels?: string[]
|
labels?: string[]
|
||||||
dataTypes?: string[]
|
dataTypes?: string[]
|
||||||
submitFunction?: (params: any) => any
|
|
||||||
submitButtonText?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Form<T>{
|
export default class Form<T>{
|
||||||
@@ -26,20 +24,16 @@ export default class Form<T>{
|
|||||||
public dataTypes: any[]
|
public dataTypes: any[]
|
||||||
public length: number;
|
public length: number;
|
||||||
public state: T;
|
public state: T;
|
||||||
public submitButtonText?: string;
|
public getState: (received: T) => void
|
||||||
public submitFunction?: (params: any) => any;
|
|
||||||
public setState: any
|
|
||||||
|
|
||||||
constructor(config: FormConfig<T>){
|
constructor(config: FormConfig<T>){
|
||||||
this.parent = config.parent;
|
this.parent = config.parent;
|
||||||
this.keys = config.keys;
|
this.keys = config.keys;
|
||||||
this.labels = config.labels || this.keys;
|
this.labels = config.labels || this.keys;
|
||||||
this.length = config.keys.length;
|
this.length = config.keys.length;
|
||||||
this.submitFunction = config.submitFunction || undefined;
|
|
||||||
this.submitButtonText = config.submitButtonText || undefined;
|
|
||||||
this.dataTypes = config.dataTypes || new Array(this.keys.length).fill('text');
|
this.dataTypes = config.dataTypes || new Array(this.keys.length).fill('text');
|
||||||
this.state = config.initialState;
|
this.state = config.initialState;
|
||||||
this.setState = config.setState;
|
this.getState = config.getState;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(e: ChangeEvent<HTMLElement>, idx: number) {
|
update(e: ChangeEvent<HTMLElement>, idx: number) {
|
||||||
@@ -49,10 +43,7 @@ export default class Form<T>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.state = newState;
|
this.state = newState;
|
||||||
this.setState(newState);
|
this.getState(newState);
|
||||||
|
|
||||||
console.log(this.state);
|
|
||||||
this.mount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mount() {
|
mount() {
|
||||||
@@ -72,12 +63,6 @@ export default class Form<T>{
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.submitFunction) {
|
|
||||||
output.push(
|
|
||||||
<button key={v4()} onClick={this.submitFunction}>{this.submitButtonText || 'Button'}</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
3
client/src/hooks/useNow.tsx
Normal file
3
client/src/hooks/useNow.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const useNow = () => {
|
||||||
|
return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'long' }).format(Date.now());
|
||||||
|
}
|
||||||
@@ -15,8 +15,13 @@ export const attemptLogin = async (data: IUserAuth) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const attemptRegister = async (data: IUser) => {
|
export const attemptRegister = async (data: IUser) => {
|
||||||
const result = await fetch(API + 'auth/register/', { method: "POST" })
|
const result = await fetch(API + 'auth/register/', {
|
||||||
.then(response => response.json());
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
}).then(response => response.json());
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,36 +2,37 @@ import { IUser, IUserAuth } from "../schemas";
|
|||||||
import { User } from "../models/user";
|
import { User } from "../models/user";
|
||||||
import createError from "http-errors";
|
import createError from "http-errors";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import { QueryResult } from "pg";
|
import now from "../util/now";
|
||||||
|
|
||||||
const UserInstance = new User();
|
const UserInstance = new User();
|
||||||
|
|
||||||
export default class AuthService {
|
export default class AuthService {
|
||||||
// methods for local strategies
|
// methods for local strategies
|
||||||
async register(data: IUser): Promise<Array<keyof IUser>> {
|
async register(data: IUser) {
|
||||||
const { email, password } = data;
|
const { email, password } = data;
|
||||||
|
|
||||||
|
data.datecreated = now;
|
||||||
|
data.datemodified = now;
|
||||||
|
data.active = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await UserInstance.getOneByEmail(email);
|
const user = await UserInstance.getOneByEmail(email);
|
||||||
if (user) throw createError('409', 'Email already in use');
|
if (user) throw createError('409', 'Email already in use');
|
||||||
|
|
||||||
let createdUser: IUser | null = null;
|
|
||||||
|
|
||||||
bcrypt.genSalt((err, salt) => {
|
bcrypt.genSalt(10, (err, salt) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
bcrypt.hash(password, salt, (err, hash) => {
|
bcrypt.hash(password, salt, async (err, hash) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
const newData = {
|
||||||
createdUser = {
|
|
||||||
...data,
|
...data,
|
||||||
password: hash
|
password: hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const result = await UserInstance.post(newData);
|
||||||
|
if (result) console.log(result);
|
||||||
|
return result;
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!createdUser) throw createError('400', 'Error creating user');
|
|
||||||
const result = await UserInstance.post(createdUser);
|
|
||||||
if (!result) throw createError('400', 'Error creating user');
|
|
||||||
return result;
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
throw new Error(e);
|
throw new Error(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default async function populate() {
|
|||||||
|
|
||||||
const populateUsers = `
|
const populateUsers = `
|
||||||
INSERT INTO recipin.appusers
|
INSERT INTO recipin.appusers
|
||||||
(firstname, lastname, handle, email, password, active, dateregistered, datelastactive)
|
(firstname, lastname, handle, email, password, active, datecreated, datemodified)
|
||||||
VALUES
|
VALUES
|
||||||
('Mikayla', 'Dobson', 'innocuoussymmetry', 'mikaylaherself@gmail.com', 'password1', true, $1, $1),
|
('Mikayla', 'Dobson', 'innocuoussymmetry', 'mikaylaherself@gmail.com', 'password1', true, $1, $1),
|
||||||
('Emily', 'Dobson', 'emjdobson', 'emily@email.com', 'password2', true, $1, $1),
|
('Emily', 'Dobson', 'emjdobson', 'emily@email.com', 'password2', true, $1, $1),
|
||||||
@@ -68,9 +68,20 @@ export default async function populate() {
|
|||||||
;
|
;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const populateComments = `
|
||||||
|
INSERT INTO recipin.cmp_recipecomments
|
||||||
|
(commentbody, datecreated, recipeid, authorid)
|
||||||
|
VALUES
|
||||||
|
('Very cool recipe!', $1, 2, 2),
|
||||||
|
('I love making this one', $1, 1, 2),
|
||||||
|
('Thanks for sharing this', $1, 1, 4)
|
||||||
|
;
|
||||||
|
`
|
||||||
|
|
||||||
const allStatements: Array<string> = [
|
const allStatements: Array<string> = [
|
||||||
populateUsers, populateRecipes, populateCollection,
|
populateUsers, populateRecipes, populateCollection,
|
||||||
populateIngredients, populateGroceryList, populateFriendships
|
populateIngredients, populateGroceryList, populateFriendships,
|
||||||
|
populateComments
|
||||||
];
|
];
|
||||||
|
|
||||||
await pool.query(setup);
|
await pool.query(setup);
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ dotenv.config();
|
|||||||
email varchar NOT NULL UNIQUE,
|
email varchar NOT NULL UNIQUE,
|
||||||
password varchar NOT NULL,
|
password varchar NOT NULL,
|
||||||
active boolean NOT NULL,
|
active boolean NOT NULL,
|
||||||
dateregistered varchar NOT NULL,
|
datecreated varchar NOT NULL,
|
||||||
datelastactive varchar NOT NULL
|
datemodified varchar NOT NULL
|
||||||
);
|
);
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -76,7 +76,8 @@ dotenv.config();
|
|||||||
CREATE TABLE IF NOT EXISTS recipin.cmp_recipecomments (
|
CREATE TABLE IF NOT EXISTS recipin.cmp_recipecomments (
|
||||||
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||||
commentbody varchar NOT NULL,
|
commentbody varchar NOT NULL,
|
||||||
parentcommentid int,
|
datecreated varchar NOT NULL,
|
||||||
|
recipeid int REFERENCES recipin.recipe (id) NOT NULL,
|
||||||
authorid int REFERENCES recipin.appusers (id) NOT NULL
|
authorid int REFERENCES recipin.appusers (id) NOT NULL
|
||||||
);
|
);
|
||||||
`
|
`
|
||||||
@@ -112,7 +113,7 @@ dotenv.config();
|
|||||||
`
|
`
|
||||||
|
|
||||||
const allStatements = [
|
const allStatements = [
|
||||||
setRole, appusers, ingredient, collection, recipe,
|
setRole, appusers, ingredient, collection, recipe, recipecomments,
|
||||||
groceryList, recipeingredient, userscollections, userfriendships
|
groceryList, recipeingredient, userscollections, userfriendships
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { IUser } from "../schemas";
|
import { IUser } from "../schemas";
|
||||||
import pgPromise from "pg-promise";
|
import pgPromise from "pg-promise";
|
||||||
import pool from '../db';
|
import pool from '../db';
|
||||||
|
import now from "../util/now";
|
||||||
|
|
||||||
const pgp = pgPromise({ capSQL: true });
|
const pgp = pgPromise({ capSQL: true });
|
||||||
export class User {
|
export class User {
|
||||||
@@ -67,13 +68,22 @@ export class User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async post(data: IUser): Promise<Array<any> | null> {
|
async post(data: IUser) {
|
||||||
const { firstname, lastname, handle, email, password, active } = data;
|
const { firstname, lastname, handle, email, password, active } = data;
|
||||||
|
const datecreated = now;
|
||||||
|
const datemodified = now;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const statement = `INSERT INTO recipin.appusers (firstname, lastname, handle, email, password, active) VALUES ($1, $2, $3, $4, $5, $6)`;
|
const statement = `
|
||||||
const params = [firstname, lastname, handle, email, password, active];
|
INSERT INTO recipin.appusers (
|
||||||
|
firstname, lastname, handle, email, password,
|
||||||
|
active, datecreated, datemodified)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||||
|
RETURNING *;
|
||||||
|
`;
|
||||||
|
const params = [firstname, lastname, handle, email, password, active, datecreated, datemodified];
|
||||||
const result = await pool.query(statement, params);
|
const result = await pool.query(statement, params);
|
||||||
if (result.rows.length) return result.rows as Array<keyof IUser>;
|
if (result.rows.length) return result.rows;
|
||||||
return null;
|
return null;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ export const authRoute = (app: Express, passport: PassportStatic) => {
|
|||||||
router.post('/register', async (req, res, next) => {
|
router.post('/register', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const data: IUser = req.body;
|
const data: IUser = req.body;
|
||||||
|
const now = new Intl.DateTimeFormat('en-US', {})
|
||||||
|
|
||||||
const response = await AuthInstance.register(data);
|
const response = await AuthInstance.register(data);
|
||||||
res.status(200).send(response);
|
res.status(200).send(response);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
|||||||
@@ -1,45 +1,48 @@
|
|||||||
export interface IUser {
|
interface HasHistory {
|
||||||
|
datecreated?: string
|
||||||
|
datemodified?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CanDeactivate {
|
||||||
|
active?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DBEntity {
|
||||||
id?: number
|
id?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUser extends DBEntity, HasHistory, CanDeactivate {
|
||||||
firstname: string
|
firstname: string
|
||||||
lastname: string
|
lastname: string
|
||||||
handle: string
|
handle: string
|
||||||
email: string
|
email: string
|
||||||
password: string
|
password: string
|
||||||
active: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IRecipe {
|
|
||||||
id?: number
|
|
||||||
name: string
|
|
||||||
description?: string
|
|
||||||
preptime: string
|
|
||||||
removed: boolean
|
|
||||||
authoruserid?: IUser["id"]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IIngredient {
|
|
||||||
id?: number
|
|
||||||
name: string
|
|
||||||
description?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICollection {
|
|
||||||
id?: number
|
|
||||||
name: string
|
|
||||||
active: string
|
|
||||||
ismaincollection: boolean
|
|
||||||
ownerid?: IUser["id"]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGroceryList {
|
|
||||||
id?: number
|
|
||||||
name: string
|
|
||||||
recipes?: IRecipe["id"][]
|
|
||||||
active: boolean
|
|
||||||
ownerid?: IUser["id"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUserAuth {
|
export interface IUserAuth {
|
||||||
email: string
|
email: string
|
||||||
password: string
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRecipe extends DBEntity, HasHistory, CanDeactivate {
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
preptime: string
|
||||||
|
authoruserid?: IUser["id"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IIngredient extends DBEntity, HasHistory {
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICollection extends DBEntity, HasHistory, CanDeactivate {
|
||||||
|
name: string
|
||||||
|
ismaincollection: boolean
|
||||||
|
ownerid?: IUser["id"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGroceryList extends DBEntity, HasHistory, CanDeactivate {
|
||||||
|
name: string
|
||||||
|
ownerid?: IUser["id"]
|
||||||
}
|
}
|
||||||
2
server/util/now.ts
Normal file
2
server/util/now.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
const now = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'long' }).format(Date.now())
|
||||||
|
export default now;
|
||||||
Reference in New Issue
Block a user