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 { attemptLogin } from "../../util/apiUtils";
import { Button, Page, Panel } from "../ui";
@@ -11,6 +11,10 @@ export default function Login() {
password: ''
})
const getFormState = useCallback((received: IUserAuth) => {
setInput(received);
}, [])
const handleLogin = async () => {
if (!input.email || !input.password) return;
const result = await attemptLogin(input);
@@ -23,9 +27,7 @@ export default function Login() {
labels: ["Email", "Password"],
dataTypes: Object.keys(input),
initialState: input,
setState: setInput,
submitButtonText: "Log In",
submitFunction: handleLogin
getState: getFormState
}
useEffect(() => {
@@ -38,6 +40,7 @@ export default function Login() {
<Panel extraStyles="form-panel">
{ form }
<Button onClick={handleLogin}>Log In</Button>
</Panel>
<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 { IUser } from "../../../schemas";
import { attemptRegister } from "../../../util/apiUtils";
import { Page, Panel } from "../../ui";
import { Button, Page, Panel } from "../../ui";
import Divider from "../../ui/Divider";
import Form, { FormConfig } from "../../ui/Form";
@@ -17,21 +17,34 @@ export default function AboutYou() {
active: true
});
const getFormState = useCallback((received: IUser) => {
setInput(received);
}, []);
const formConfig: FormConfig<IUser> = {
parent: "register",
keys: Object.keys(input),
keys: ['firstname', 'lastname', 'handle', 'email', 'password'],
initialState: input,
labels: ['First Name', 'Last Name', 'Handle', 'Email', "Password", "Active?"],
dataTypes: ['text', 'text', 'text', 'email', 'password', 'text'],
setState: setInput,
submitButtonText: 'Register',
submitFunction: () => console.log(input)
labels: ['First Name', 'Last Name', 'Handle', 'Email', "Password"],
dataTypes: ['text', 'text', 'text', 'email', 'password'],
getState: getFormState
}
useEffect(() => {
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 (
<Page>
<h1>Hi! Thanks for being here.</h1>
@@ -42,6 +55,7 @@ export default function AboutYou() {
<Panel extraStyles="form-panel two-columns">
{ form }
<Button onClick={handleRegister}>Register</Button>
</Panel>
</Page>
)

View File

@@ -1,4 +1,4 @@
import { ChangeEvent, Dispatch, SetStateAction, useState } from "react";
import { ChangeEvent } from "react";
import { v4 } from 'uuid';
/**
@@ -12,11 +12,9 @@ export interface FormConfig<T> {
parent: string
keys: string[]
initialState: T
setState: Dispatch<SetStateAction<T>>
getState: (received: T) => void
labels?: string[]
dataTypes?: string[]
submitFunction?: (params: any) => any
submitButtonText?: string
}
export default class Form<T>{
@@ -26,20 +24,16 @@ export default class Form<T>{
public dataTypes: any[]
public length: number;
public state: T;
public submitButtonText?: string;
public submitFunction?: (params: any) => any;
public setState: any
public getState: (received: T) => void
constructor(config: FormConfig<T>){
this.parent = config.parent;
this.keys = config.keys;
this.labels = config.labels || this.keys;
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.state = config.initialState;
this.setState = config.setState;
this.getState = config.getState;
}
update(e: ChangeEvent<HTMLElement>, idx: number) {
@@ -49,10 +43,7 @@ export default class Form<T>{
}
this.state = newState;
this.setState(newState);
console.log(this.state);
this.mount();
this.getState(newState);
}
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;
}
}

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) => {
const result = await fetch(API + 'auth/register/', { method: "POST" })
.then(response => response.json());
const result = await fetch(API + 'auth/register/', {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
}).then(response => response.json());
return result;
}

View File

@@ -2,36 +2,37 @@ import { IUser, IUserAuth } from "../schemas";
import { User } from "../models/user";
import createError from "http-errors";
import bcrypt from "bcrypt";
import { QueryResult } from "pg";
import now from "../util/now";
const UserInstance = new User();
export default class AuthService {
// methods for local strategies
async register(data: IUser): Promise<Array<keyof IUser>> {
async register(data: IUser) {
const { email, password } = data;
data.datecreated = now;
data.datemodified = now;
data.active = true;
try {
const user = await UserInstance.getOneByEmail(email);
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;
bcrypt.hash(password, salt, (err, hash) => {
bcrypt.hash(password, salt, async (err, hash) => {
if (err) throw err;
createdUser = {
const newData = {
...data,
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) {
throw new Error(e);
}

View File

@@ -6,7 +6,7 @@ export default async function populate() {
const populateUsers = `
INSERT INTO recipin.appusers
(firstname, lastname, handle, email, password, active, dateregistered, datelastactive)
(firstname, lastname, handle, email, password, active, datecreated, datemodified)
VALUES
('Mikayla', 'Dobson', 'innocuoussymmetry', 'mikaylaherself@gmail.com', 'password1', 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> = [
populateUsers, populateRecipes, populateCollection,
populateIngredients, populateGroceryList, populateFriendships
populateIngredients, populateGroceryList, populateFriendships,
populateComments
];
await pool.query(setup);

View File

@@ -21,8 +21,8 @@ dotenv.config();
email varchar NOT NULL UNIQUE,
password varchar NOT NULL,
active boolean NOT NULL,
dateregistered varchar NOT NULL,
datelastactive varchar NOT NULL
datecreated varchar NOT NULL,
datemodified varchar NOT NULL
);
`
@@ -76,7 +76,8 @@ dotenv.config();
CREATE TABLE IF NOT EXISTS recipin.cmp_recipecomments (
id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
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
);
`
@@ -112,7 +113,7 @@ dotenv.config();
`
const allStatements = [
setRole, appusers, ingredient, collection, recipe,
setRole, appusers, ingredient, collection, recipe, recipecomments,
groceryList, recipeingredient, userscollections, userfriendships
]

View File

@@ -1,6 +1,7 @@
import { IUser } from "../schemas";
import pgPromise from "pg-promise";
import pool from '../db';
import now from "../util/now";
const pgp = pgPromise({ capSQL: true });
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 datecreated = now;
const datemodified = now;
try {
const statement = `INSERT INTO recipin.appusers (firstname, lastname, handle, email, password, active) VALUES ($1, $2, $3, $4, $5, $6)`;
const params = [firstname, lastname, handle, email, password, active];
const statement = `
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);
if (result.rows.length) return result.rows as Array<keyof IUser>;
if (result.rows.length) return result.rows;
return null;
} catch (error: any) {
throw new Error(error);

View File

@@ -23,6 +23,8 @@ export const authRoute = (app: Express, passport: PassportStatic) => {
router.post('/register', async (req, res, next) => {
try {
const data: IUser = req.body;
const now = new Intl.DateTimeFormat('en-US', {})
const response = await AuthInstance.register(data);
res.status(200).send(response);
} 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
}
export interface IUser extends DBEntity, HasHistory, CanDeactivate {
firstname: string
lastname: string
handle: string
email: 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 {
email: 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;