From 28c84efcd5f3fba656fd6739d5d9cc9f9e74b695 Mon Sep 17 00:00:00 2001
From: Mikayla Dobson <93477693+innocuous-symmetry@users.noreply.github.com>
Date: Mon, 21 Nov 2022 15:05:57 -0600
Subject: [PATCH] user registration connects to front end
---
client/src/components/pages/Login.tsx | 11 +--
.../pages/Register/register.aboutyou.tsx | 30 +++++---
client/src/components/ui/Form.tsx | 25 ++-----
client/src/hooks/useNow.tsx | 3 +
client/src/util/apiUtils.ts | 9 ++-
server/auth/index.ts | 27 ++++----
server/db/examplevals.ts | 15 +++-
server/db/seed.ts | 9 +--
server/models/user.ts | 18 +++--
server/routes/auth.ts | 2 +
server/schemas/index.ts | 69 ++++++++++---------
server/util/now.ts | 2 +
12 files changed, 130 insertions(+), 90 deletions(-)
create mode 100644 client/src/hooks/useNow.tsx
create mode 100644 server/util/now.ts
diff --git a/client/src/components/pages/Login.tsx b/client/src/components/pages/Login.tsx
index c63cb0a..95c4e12 100644
--- a/client/src/components/pages/Login.tsx
+++ b/client/src/components/pages/Login.tsx
@@ -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() {
{ form }
+
diff --git a/client/src/components/pages/Register/register.aboutyou.tsx b/client/src/components/pages/Register/register.aboutyou.tsx
index b6c741f..c397c1e 100644
--- a/client/src/components/pages/Register/register.aboutyou.tsx
+++ b/client/src/components/pages/Register/register.aboutyou.tsx
@@ -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 = {
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(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 (
Hi! Thanks for being here.
@@ -42,6 +55,7 @@ export default function AboutYou() {
{ form }
+
)
diff --git a/client/src/components/ui/Form.tsx b/client/src/components/ui/Form.tsx
index a24db07..f88b3e4 100644
--- a/client/src/components/ui/Form.tsx
+++ b/client/src/components/ui/Form.tsx
@@ -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 {
parent: string
keys: string[]
initialState: T
- setState: Dispatch>
+ getState: (received: T) => void
labels?: string[]
dataTypes?: string[]
- submitFunction?: (params: any) => any
- submitButtonText?: string
}
export default class Form{
@@ -26,20 +24,16 @@ export default class Form{
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){
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, idx: number) {
@@ -49,10 +43,7 @@ export default class Form{
}
this.state = newState;
- this.setState(newState);
-
- console.log(this.state);
- this.mount();
+ this.getState(newState);
}
mount() {
@@ -72,12 +63,6 @@ export default class Form{
)
}
- if (this.submitFunction) {
- output.push(
-
- )
- }
-
return output;
}
}
\ No newline at end of file
diff --git a/client/src/hooks/useNow.tsx b/client/src/hooks/useNow.tsx
new file mode 100644
index 0000000..87ceb86
--- /dev/null
+++ b/client/src/hooks/useNow.tsx
@@ -0,0 +1,3 @@
+export const useNow = () => {
+ return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'long' }).format(Date.now());
+}
\ No newline at end of file
diff --git a/client/src/util/apiUtils.ts b/client/src/util/apiUtils.ts
index 06da29b..e8ed47a 100644
--- a/client/src/util/apiUtils.ts
+++ b/client/src/util/apiUtils.ts
@@ -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;
}
diff --git a/server/auth/index.ts b/server/auth/index.ts
index b596a62..d8c86ff 100644
--- a/server/auth/index.ts
+++ b/server/auth/index.ts
@@ -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> {
+ 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);
}
diff --git a/server/db/examplevals.ts b/server/db/examplevals.ts
index 5370bbf..8f10da6 100644
--- a/server/db/examplevals.ts
+++ b/server/db/examplevals.ts
@@ -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 = [
populateUsers, populateRecipes, populateCollection,
- populateIngredients, populateGroceryList, populateFriendships
+ populateIngredients, populateGroceryList, populateFriendships,
+ populateComments
];
await pool.query(setup);
diff --git a/server/db/seed.ts b/server/db/seed.ts
index c2dd2fb..4f3f81d 100644
--- a/server/db/seed.ts
+++ b/server/db/seed.ts
@@ -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
]
diff --git a/server/models/user.ts b/server/models/user.ts
index 066335f..c834e9e 100644
--- a/server/models/user.ts
+++ b/server/models/user.ts
@@ -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 | 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;
+ if (result.rows.length) return result.rows;
return null;
} catch (error: any) {
throw new Error(error);
diff --git a/server/routes/auth.ts b/server/routes/auth.ts
index 881fb8b..056989a 100644
--- a/server/routes/auth.ts
+++ b/server/routes/auth.ts
@@ -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) {
diff --git a/server/schemas/index.ts b/server/schemas/index.ts
index f4575ec..bced4cb 100644
--- a/server/schemas/index.ts
+++ b/server/schemas/index.ts
@@ -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"]
}
\ No newline at end of file
diff --git a/server/util/now.ts b/server/util/now.ts
new file mode 100644
index 0000000..bd37cfb
--- /dev/null
+++ b/server/util/now.ts
@@ -0,0 +1,2 @@
+const now = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'long' }).format(Date.now())
+export default now;
\ No newline at end of file