user registration connects to front end

This commit is contained in:
Mikayla Dobson
2022-11-21 15:05:57 -06:00
parent 48f6a60e77
commit 28c84efcd5
12 changed files with 130 additions and 90 deletions

View File

@@ -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>

View File

@@ -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>
) )

View File

@@ -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;
} }
} }

View File

@@ -0,0 +1,3 @@
export const useNow = () => {
return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'long' }).format(Date.now());
}

View File

@@ -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;
} }

View File

@@ -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);
} }

View File

@@ -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);

View File

@@ -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
] ]

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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
View File

@@ -0,0 +1,2 @@
const now = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'long' }).format(Date.now())
export default now;