diff --git a/README.md b/README.md index e3b34c1..733eb52 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,9 @@ The current KinTree project team as of Fall 2025 includes Andrea Ambrose, Matthe ### Prerequisites -Node.js (install the correct version for your own OS [here](https://nodejs.org/en)) and get your Supabase URL, CLIENT KEY, and SERVICE KEY from your project dashboard [here](https://supabase.com/) +Node.js (install the correct version for your own OS [here](https://nodejs.org/en)) and install MySQL [here](https://dev.mysql.com/downloads/mysql/). Set up account information through the Configurator application or through the terminal. -### Database Setup - -In the /server/ folder, add an .env file with variables `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY`. -In the /client/ folder, add an .env file with variables `REACT_APP_SUPABASE_URL` and `REACT_APP_SUPABASE_ANON_KEY`. - -### Web Application Setup +### Setup To set up the KinTree codebase on your own machine, start by cloning the repository to your local file system. @@ -39,3 +34,24 @@ Then, from the same directory, run the following command to run the server/API: `node server.js` +### Database Setup + +Open the MySQL Client Terminal, login with your password to run the mySQL server. + +Create a new database instance on your machine: +`CREATE DATABASE ` + +In the /server/ directory, create a .env file with MySQL information. Example env is in the project's /docs/ folder. + +Open another command line window in /SeniorProject_KinTree/server/ and run the command `npm install knex mysql2` to install Knex and mySQL2. + +Verify the connection: + +`node mysql-connection.js` + +Ensure proper migration files are loaded: + +`npx knex:migrate status` + +Run the command `npx knex migrate:latest` to create and/or update existing database tables. + diff --git a/client/package-lock.json b/client/package-lock.json index af787b0..782fd83 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "dependencies": { "@hookform/resolvers": "^4.1.3", - "@supabase/supabase-js": "^2.75.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -3701,123 +3700,6 @@ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", "license": "MIT" }, - "node_modules/@supabase/auth-js": { - "version": "2.75.0", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.75.0.tgz", - "integrity": "sha512-J8TkeqCOMCV4KwGKVoxmEBuDdHRwoInML2vJilthOo7awVCro2SM+tOcpljORwuBQ1vHUtV62Leit+5wlxrNtw==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "2.6.15" - } - }, - "node_modules/@supabase/functions-js": { - "version": "2.75.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.75.0.tgz", - "integrity": "sha512-18yk07Moj/xtQ28zkqswxDavXC3vbOwt1hDuYM3/7xPnwwpKnsmPyZ7bQ5th4uqiJzQ135t74La9tuaxBR6e7w==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "2.6.15" - } - }, - "node_modules/@supabase/node-fetch": { - "version": "2.6.15", - "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", - "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/@supabase/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/@supabase/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/@supabase/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/@supabase/postgrest-js": { - "version": "2.75.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.75.0.tgz", - "integrity": "sha512-YfBz4W/z7eYCFyuvHhfjOTTzRrQIvsMG2bVwJAKEVVUqGdzqfvyidXssLBG0Fqlql1zJFgtsPpK1n4meHrI7tg==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "2.6.15" - } - }, - "node_modules/@supabase/realtime-js": { - "version": "2.75.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.75.0.tgz", - "integrity": "sha512-B4Xxsf2NHd5cEnM6MGswOSPSsZKljkYXpvzKKmNxoUmNQOfB7D8HOa6NwHcUBSlxcjV+vIrYKcYXtavGJqeGrw==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "2.6.15", - "@types/phoenix": "^1.6.6", - "@types/ws": "^8.18.1", - "ws": "^8.18.2" - } - }, - "node_modules/@supabase/realtime-js/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@supabase/storage-js": { - "version": "2.75.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.75.0.tgz", - "integrity": "sha512-wpJMYdfFDckDiHQaTpK+Ib14N/O2o0AAWWhguKvmmMurB6Unx17GGmYp5rrrqCTf8S1qq4IfIxTXxS4hzrUySg==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "2.6.15" - } - }, - "node_modules/@supabase/supabase-js": { - "version": "2.75.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.75.0.tgz", - "integrity": "sha512-8UN/vATSgS2JFuJlMVr51L3eUDz+j1m7Ww63wlvHLKULzCDaVWYzvacCjBTLW/lX/vedI2LBI4Vg+01G9ufsJQ==", - "license": "MIT", - "dependencies": { - "@supabase/auth-js": "2.75.0", - "@supabase/functions-js": "2.75.0", - "@supabase/node-fetch": "2.6.15", - "@supabase/postgrest-js": "2.75.0", - "@supabase/realtime-js": "2.75.0", - "@supabase/storage-js": "2.75.0" - } - }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -4883,12 +4765,6 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "license": "MIT" }, - "node_modules/@types/phoenix": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", - "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", - "license": "MIT" - }, "node_modules/@types/prettier": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", @@ -5020,9 +4896,9 @@ "license": "MIT" }, "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", "license": "MIT", "dependencies": { "@types/node": "*" diff --git a/client/package.json b/client/package.json index 396ed46..70c255b 100644 --- a/client/package.json +++ b/client/package.json @@ -4,7 +4,6 @@ "private": true, "dependencies": { "@hookform/resolvers": "^4.1.3", - "@supabase/supabase-js": "^2.75.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/client/src/CurrentUserProvider.js b/client/src/CurrentUserProvider.js index 510cc5e..45a788e 100644 --- a/client/src/CurrentUserProvider.js +++ b/client/src/CurrentUserProvider.js @@ -1,14 +1,13 @@ import { React, useState, createContext, useContext, useEffect } from "react" -import { supabase } from "./utils/supabaseClient"; +import { set } from "react-hook-form"; export const currentContext = createContext(); export const CurrentUserProvider = ({ children }) => { const [currentUserID, setCurrentUserIDState] = useState(''); - const [currentAccountID, setCurrentAccountIDState] = useState(''); + const [currentAccountID, setCurrentAccountIDState] = useState(''); // TODO login will set this const [currentUserName, setCurrentUserNameState] = useState(''); const [loading, setLoading] = useState(true); - const [supabaseUser, setSupabaseUser] = useState(null); const setCurrentAccountID = (accountID) => { // logging in will trigger this localStorage.setItem("currentAccountID", accountID); @@ -53,80 +52,29 @@ export const CurrentUserProvider = ({ children }) => { } - // Initialize Supabase auth state + // init useEffect(() => { - const initializeAuth = async () => { - try { - // Get initial session - const { data: { session } } = await supabase.auth.getSession(); - - if (session?.user) { - setSupabaseUser(session.user); - setCurrentAccountIDState(session.user.id); - setCurrentUserNameState(session.user.email); // Use email as default username - } - - setLoading(false); - } catch (error) { - console.error('Error initializing auth:', error); - setLoading(false); - } - }; + const initializeState = () => { + const storedAccountID = localStorage.getItem("currentAccountID"); + const storedUserID = localStorage.getItem("currentUserID"); + const storedUserName = localStorage.getItem("currentUserName"); - initializeAuth(); - - // Listen for auth changes - const { data: { subscription } } = supabase.auth.onAuthStateChange( - async (event, session) => { - if (session?.user) { - setSupabaseUser(session.user); - setCurrentAccountIDState(session.user.id); - setCurrentUserNameState(session.user.email); - // Auto-sync profile into public.users using auth metadata when available - try { - const m = session.user.user_metadata || {}; - await fetch('http://localhost:5000/api/auth/sync', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - auth_uid: session.user.id, - email: session.user.email, - username: session.user.email, - firstName: m.firstName || m.first_name || null, - lastName: m.lastName || m.last_name || null, - phoneNumber: m.phoneNumber || m.phone_number || m.phonenum || null, - birthDate: m.birthDate || m.birthdate || null, - }) - }); - } catch (e) { - console.warn('Auth sync failed:', e?.message || e); - } - } else { - setSupabaseUser(null); - setCurrentAccountIDState(''); - setCurrentUserNameState(''); - } - setLoading(false); + if (storedAccountID) { + setCurrentAccountIDState(storedAccountID); } - ); - - return () => subscription.unsubscribe(); + if (storedUserID) { + setCurrentUserIDState(storedUserID); + } + if (storedUserName) { + setCurrentUserNameState(storedUserName); + } + setLoading(false); + }; + initializeState(); }, []); return ( - + {children} ) diff --git a/client/src/components/EditFamilyMember/EditFamilyMember.js b/client/src/components/EditFamilyMember/EditFamilyMember.js new file mode 100644 index 0000000..2d4369c --- /dev/null +++ b/client/src/components/EditFamilyMember/EditFamilyMember.js @@ -0,0 +1,254 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import Popup from 'reactjs-popup'; +import 'reactjs-popup/dist/index.css'; +import { useForm } from 'react-hook-form'; +import * as styles from './styles'; +import './popup.css'; +import { ReactComponent as CloseIcon } from '../../assets/exit.svg'; +import { useCurrentUser } from '../../CurrentUserProvider'; + +function EditFamilyMemberPopup({ trigger, memberId, onSaved }) { + const { currentUserID } = useCurrentUser(); + + const [loading, setLoading] = useState(true); + const [view, setView] = useState("basic"); + + const [memberData, setMemberData] = useState(null); + const [relationshipData, setRelationshipData] = useState(null); + + const matPat = useMemo(() => ["parent", "cousin", "aunt", "uncle", "grandparent", "niece", "nephew"], []); + + + + const { + register, + handleSubmit, + reset + } = useForm(); + + const { + register: registerRel, + handleSubmit: handleSubmitRel, + reset: resetRel + } = useForm(); + + + + useEffect(() => { + const load = async () => { + try { + setLoading(true); + + const memberRes = await fetch(`http://localhost:5000/api/family-members/${memberId}`); + const memberJson = await memberRes.json(); + + const relRes = await fetch(`http://localhost:5000/api/relationships/member/${memberId}`); + const relJson = await relRes.json(); + + setMemberData(memberJson); + setRelationshipData(relJson); + + // preload forms + reset({ + firstName: memberJson.firstName, + lastName: memberJson.lastName, + location: memberJson.location, + birthday: memberJson.birthDate, + birthplace: memberJson.birthplace, + deathdate: memberJson.deathDate + }); + + resetRel({ + relationship: relJson.relationshipType, + matPat: relJson.side + }); + + } catch (err) { + console.error(err); + } + + setLoading(false); + }; + + if (memberId) load(); + }, [memberId, reset, resetRel]); + + // ----------- CLOSE HANDLER ----------- + + const closeModal = (close) => { + reset(); + resetRel(); + close(); + }; + + // ----------- SAVE BASIC INFO ----------- + + const onSubmitBasic = async (data) => { + try { + const res = await fetch(`http://localhost:5000/api/family-members/${memberId}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + firstName: data.firstName, + lastName: data.lastName, + location: data.location || null, + birthDate: data.birthday || null, + birthplace: data.birthplace || null, + deathDate: data.deathdate || null + }) + }); + + if (res.ok) onSaved && onSaved(); + } catch (err) { + console.error(err); + } + }; + + + + const onSubmitRelationship = async (data) => { + try { + const res = await fetch(`http://localhost:5000/api/relationships/${relationshipData.id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + relationshipType: data.relationship, + side: matPat.includes(data.relationship) ? data.matPat : null, + relationshipStatus: "active" + }) + }); + + if (res.ok) onSaved && onSaved(); + + } catch (err) { + console.error(err); + } + }; + + if (loading) return null; + + return ( + + {(close) => ( +
+ + {/* Close button */} +
+ +
+ +

+ Edit Family Member +

+ + {/* Tab Selector */} +
+ + +
+ + {/* BASIC INFO FORM */} + {view === "basic" && ( +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+ +
+ +
+
+ )} + + {/* RELATIONSHIP FORM */} + {view === "relationship" && ( +
+
    +
  • + +
  • + + {/* Maternal/paternal only shown when needed */} + {matPat.includes(relationshipData.relationshipType) && ( +
  • + Maternal + Paternal +
  • + )} +
+ +
+ +
+
+ )} + +
+ )} +
+ ); +} + +export default EditFamilyMemberPopup; \ No newline at end of file diff --git a/client/src/components/EditFamilyMember/popup.css b/client/src/components/EditFamilyMember/popup.css new file mode 100644 index 0000000..c230a6a --- /dev/null +++ b/client/src/components/EditFamilyMember/popup.css @@ -0,0 +1,5 @@ +.popup-content { + width: auto; + border-radius: 30px; + min-width: 0px; +} \ No newline at end of file diff --git a/client/src/components/EditFamilyMember/styles.js b/client/src/components/EditFamilyMember/styles.js new file mode 100644 index 0000000..80059fe --- /dev/null +++ b/client/src/components/EditFamilyMember/styles.js @@ -0,0 +1,99 @@ +export const DefaultStyle = { + fontFamily: 'Alata', +}; + +export const FieldStyle = { + borderRadius: '5px', + border: '1px solid #000000', + marginLeft: '10px' +}; + +export const ListStyle = { + listStyleType: 'none', + fontFamily: 'Alata', + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-end', + marginRight: '15%' +}; + +export const ButtonDivStyle = { + fontFamily: 'Alata', + display: 'flex', + justifyContent: 'center', +} + +export const ButtonStyle = { + fontFamily: 'Alata', + backgroundColor: '#3a5a40', + color: 'white', + borderRadius: '20px', + border: 'none', + padding: '10px 30px', + margin: '10px', + cursor: 'pointer', +} + +export const GrayButtonStyle = { + fontFamily: 'Alata', + backgroundColor: '#D9D9D9', + color: 'black', + borderRadius: '20px', + border: 'none', + padding: '10px 20px', + margin: '10px', + cursor: 'pointer', + display: 'flex', + flexDirection: 'row', + boxShadow: 'gray 0px 10px 10px -8px', +} + +export const FormStyle = { + padding: '2vw', + paddingTop: '0px', + minWidth: '360px', +} + +export const ItemStyle = { + margin: '10px 0px' +} + +export const DateFieldStyle = { + borderRadius: '5px', + border: '1px solid #000000', + marginLeft: '10px', + width: '147px', + fontFamily: 'Alata' +}; + + +export const MainContainerStyle = { + display: 'flex', + flexDirection: 'column', + padding: '2vw', + paddingTop: '0px', + alignItems: 'center', + minWidth: '360px', + minHeight: '150px', + justifyContent: 'space-between', +} + +export const AddOptionsStyle = { + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + width: '90%', + padding: '10px', + fontFamily: 'Alata', + // border: '1px solid gray', + // borderRadius: '5px', + marginTop: '10px', + height: '200px', + overflow: 'auto' +} + +export const ListingStyle = { + padding: '10px', + border: '1px solid gray', + width: '90%' +} \ No newline at end of file diff --git a/client/src/components/ProtectedRoute/ProtectedRoute.js b/client/src/components/ProtectedRoute/ProtectedRoute.js index 15b1b88..a1db22c 100644 --- a/client/src/components/ProtectedRoute/ProtectedRoute.js +++ b/client/src/components/ProtectedRoute/ProtectedRoute.js @@ -3,14 +3,14 @@ import { Navigate } from 'react-router-dom'; import { useCurrentUser } from '../../CurrentUserProvider'; function ProtectedRoute({ children }) { - const { supabaseUser, loading } = useCurrentUser(); + const { currentAccountID, loading } = useCurrentUser(); if (loading) { return
Loading...
; } // redirect to login - if (!supabaseUser) { + if (!currentAccountID) { return ; } diff --git a/client/src/index.js b/client/src/index.js index 2cda063..f720285 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -8,8 +8,6 @@ import Home from './pages/Home/Home'; import Account from './pages/Account/Account'; import Tree from './pages/Tree/Tree'; import Login from './pages/Login/Login'; -import ResetPassword from './pages/Reset/Reset'; -import UpdatePassword from './pages/Reset/UpdatePassword'; import Family from './pages/Family/Family'; import ShareTree from './pages/Tree/ShareTree/ShareTree'; import ViewSharedTrees from './pages/Tree/ViewSharedTrees/ViewSharedTrees'; @@ -57,16 +55,6 @@ const router = createBrowserRouter([ path: '/register', element: , }, - - { - path: '/reset-password', - element: , - }, - { - path: '/update-password', - element: , - }, - { path: '/tree', element: ( diff --git a/client/src/pages/Account/Account.js b/client/src/pages/Account/Account.js index a119abb..00081f2 100644 --- a/client/src/pages/Account/Account.js +++ b/client/src/pages/Account/Account.js @@ -1,79 +1,53 @@ import { React, useEffect, useState } from 'react'; import * as styles from './styles'; -import { useParams, useNavigate } from 'react-router-dom'; +import { Link, useParams } from 'react-router-dom'; import NavBar from '../../components/NavBar/NavBar'; import AddToTreePopup from '../../components/AddToTree/AddToTree'; -import { useCurrentUser } from '../../CurrentUserProvider'; +import { CurrentUserProvider, useCurrentUser } from '../../CurrentUserProvider'; function Account() { - const navigate = useNavigate(); // used to change route without refreshing page, used to prevent infinite refreshes const [ownAccount, setOwnAccount] = useState(false); // will be retrieved const [existsInTree, setExistsInTree] = useState(false); // will be retrieved const [relationshipType, setRelationshipType] = useState(''); // will be retrieved - const { currentUserID, supabaseUser, loading } = useCurrentUser(); - - // Redirect to login if not authenticated + const { currentUserID, fetchCurrentUserID, currentAccountID } = useCurrentUser(); useEffect(() => { - if (!loading && !supabaseUser) { - navigate('/login'); - } - }, [loading, supabaseUser, navigate]); + // define a regular function to call the async function + const fetchData = async () => { + await fetchCurrentUserID(); + }; + + fetchData(); + }, [fetchCurrentUserID]); // takes id from url path let { id } = useParams(); // if no id is provided, retrieve current user's id and show that page useEffect(() => { - if (!id && supabaseUser?.id) { - setOwnAccount(true); - navigate(`/account/${supabaseUser.id}`, { replace: true }); + if (!id) { + id = currentUserID; + setOwnAccount(true); + window.location.href = `/account/${currentUserID}`; } - }, [id, supabaseUser?.id, navigate]); + }, [id, currentUserID]); // TODO: query for data of account user & verify that userID of logged in user matches - const [userData, setUserData] = useState({ id: id, - firstName: 'Loading...', - lastName: '', - email: '', - birthdate: '', - address: '', - city: '', - state: '', - country: '', - phone_number: '', - zipcode: '' + username: 'Loading...', }) - // Fetch user info - check if it's a Supabase user or family member + // fetch user info + const requestOptions = { + method: 'GET', + headers: { 'Content-Type': 'application/json' } + }; + + // find this person's account info useEffect(() => { if (!id) return; - // Check if this is the current Supabase user - if (id === supabaseUser?.id) { - console.log('Supabase user data:', supabaseUser); - console.log('User metadata:', supabaseUser.user_metadata); - - setUserData({ - id: supabaseUser.id, - firstName: supabaseUser.user_metadata?.first_name || 'User', - lastName: supabaseUser.user_metadata?.last_name || '', - email: supabaseUser.email, - birthdate: supabaseUser.user_metadata?.birthdate || '', - address: supabaseUser.user_metadata?.address || '', - city: supabaseUser.user_metadata?.city || '', - state: supabaseUser.user_metadata?.state || '', - country: supabaseUser.user_metadata?.country || '', - phone_number: supabaseUser.user_metadata?.phone_number || '', - zipcode: supabaseUser.user_metadata?.zipcode || '' - }); - setOwnAccount(true); - return; - } - - // Otherwise, try to fetch from family members API const requestOptions = { method: 'GET', headers: { 'Content-Type': 'application/json' }, @@ -86,38 +60,26 @@ function Account() { setUserData(data); } else { console.error('Error fetching user data:', response); - // If family member not found, show basic info - setUserData({ - id: id, - firstName: 'Unknown', - lastName: 'User', - email: '', - }); } }) .catch((error) => { console.error('There was a problem with the fetch operation:', error); }); - }, [id, supabaseUser]); + }, [id]); useEffect(() => { - // Check if this is the current user's own account - if (id === supabaseUser?.id) { - setOwnAccount(true); - return; - } - - // If it's not the current user, check relationships (only for family members) - if (!userData.memberUserId) { + if(!userData.memberUserId){ setOwnAccount(false); + } + else if(userData.userId === userData.memberUserId) { + // don't fetch relationship + setOwnAccount(true); return; } - const requestOptions = { method: 'GET', headers: { 'Content-Type': 'application/json' }, }; - // if not self, determine relationship to user fetch(`http://localhost:5000/api/relationships/${id}`, requestOptions) .then(async(response) => { @@ -128,7 +90,7 @@ function Account() { if(relationships[i].person1_id === parseInt(currentUserID) && relationships[i].person2_id === parseInt(id)) { // this is the relationship setRelationshipType(relationships[i].relationshipType); - return; + return; // check this } } } @@ -141,18 +103,18 @@ function Account() { .catch(error => { console.error('There was a problem with the fetch operation:', error); }); - }, [id, currentUserID, userData.id, userData.memberUserId, supabaseUser?.id]); + }, [id, currentUserID, userData.id]); // check if user exists in tree useEffect(() => { - if (!id || !supabaseUser?.id) return; + if (!id || !currentAccountID) return; const requestOptions = { method: 'GET', headers: { 'Content-Type': 'application/json' }, }; - fetch(`http://localhost:5000/api/tree-info/${supabaseUser.id}`, requestOptions) + fetch(`http://localhost:5000/api/tree-info/${currentAccountID}`, requestOptions) .then(async (response) => { if (response.ok) { console.log("tree info response"); @@ -173,7 +135,7 @@ function Account() { .catch((error) => { console.error('There was a problem with the fetch operation:', error); }); - }, [id, supabaseUser?.id]); + }, [id, currentAccountID]); return (
@@ -192,7 +154,7 @@ function Account() { {/* if someone else's account, show buttons */} {!ownAccount && (
- Add To Tree} accountUserName={userData.firstName} accountUserId={id} userId={supabaseUser?.id} currentUserAccountRelationshipType={relationshipType} /> + Add To Tree} accountUserName={userData.firstName} accountUserId={id} userId={currentUserID} currentUserAccountRelationshipType={relationshipType} />
)} @@ -201,43 +163,6 @@ function Account() { {/* divider line */}
- - {/* User Information Section */} -
-

Profile Information

- -
- {/* Basic Info */} -
-

Basic Information

-
-
Email: {userData?.email || 'Not provided'}
- {userData?.birthdate &&
Birth Date: {new Date(userData.birthdate).toLocaleDateString()}
} - {userData?.phone_number &&
Phone: {userData.phone_number}
} -
-
- - {/* Address Info */} -
-

Address Information

-
- {userData?.address &&
Address: {userData.address}
} - {(userData?.city || userData?.state) && ( -
City, State: {[userData.city, userData.state].filter(Boolean).join(', ')}
- )} - {userData?.zipcode &&
ZIP Code: {userData.zipcode}
} - {userData?.country &&
Country: {userData.country}
} -
-
-
- - {/* Show message if no additional info is available */} - {!userData?.birthdate && !userData?.phone_number && !userData?.address && !userData?.city && !userData?.state && !userData?.zipcode && !userData?.country && ( -
- No additional profile information available. Update your profile to add more details. -
- )} -
diff --git a/client/src/pages/CreateAccount/CreateAccount.js b/client/src/pages/CreateAccount/CreateAccount.js index ef1e39e..9fdc592 100644 --- a/client/src/pages/CreateAccount/CreateAccount.js +++ b/client/src/pages/CreateAccount/CreateAccount.js @@ -1,7 +1,7 @@ -import { useForm } from 'react-hook-form' +import { set, useForm } from 'react-hook-form' import { React, useState } from 'react' +import { Link } from 'react-router-dom' import { yupResolver } from "@hookform/resolvers/yup" -import { handleRegister } from '../../utils/authHandlers'; import * as yup from "yup" import * as styles from './styles' import logo from '../../assets/kintreelogo-adobe.png'; @@ -23,63 +23,109 @@ const yupValidation = yup.object().shape( country: yup.string().required("Country of residence is a required field."), phonenum: yup.string() .matches( - /^(\+\d{1,2}\s?)?1?-?\.?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/ + /^(\+\d{1,2}\s?)?1?\-?\.?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/ , "Invalid phone number format." ), zipcode: yup.string().matches(/^\d{5}(?:[-\s]\d{4})?$/, "Invalid zip code format."), - password: yup.string().required("Password is required") + password: yup.string().required("Password is a required field.") .matches( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9])(?=.{8,})/, - "Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character" + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{8,})/ + , "Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character" ) + } ); const CreateAccount = () => { const {register, handleSubmit, formState: {errors}} = useForm({resolver: yupResolver(yupValidation)}); - const [errorMessage, setErrorMessage] = useState(""); const [isHovering, setIsHovering] = useState(false); + const [formData, setFormData] = useState({}); - const onSubmit = async (data) => { - setErrorMessage(""); // clear previous errors - try { - const user = await handleRegister(data.email, data.password, { - first_name: data.firstname, - last_name: data.lastname, - birthdate: data.birthdate, - address: data.address, - city: data.city, - state: data.state, - country: data.country, - phone_number: data.phonenum, - zipcode: data.zipcode - }); // frontend Supabase registration - - // TODO: store additional info in mysqldatabase later - // await fetch('http://localhost:5000/api/users', { - // method: 'POST', - // headers: { 'Content-Type': 'application/json' }, - // body: JSON.stringify({ - // userId: user.id, - // firstname: data.firstname, - // lastname: data.lastname, - // birthdate: data.birthdate, - // address: data.address, - // city: data.city, - // state: data.state, - // zipcode: data.zipcode, - // country: data.country, - // phonenum: data.phonenum - // }) - // }); - - console.log('Registration successful:', user); - window.location.href = '/login'; // redirect after registration to login - } catch (error) { - setErrorMessage(error.message); - console.error('Password:', data.password); - } - }; + const onSubmit = (data) => { + console.log(data); + + // register account + fetch(`http://localhost:5000/api/auth/register`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username: data.firstname + " " + data.lastname, + email: data.email, + password: data.password, + }), + }) + .then(async (response) => { + if (response.ok) { + const responseData = await response.json(); + console.log(responseData); + + // Use responseData.user directly + const accountID = responseData.user; + + // Initialize user's tree by adding themself + return fetch(`http://localhost:5000/api/family-members/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + firstName: data.firstname, + lastName: data.lastname, + birthdate: data.birthdate, + email: data.email, + location: `${data.address}, ${data.city}, ${data.state} ${data.zipcode}, ${data.country}`, + phoneNumber: data.phonenum, + userId: accountID, + memberUserId: accountID, + }), + }).then(async (response) => { + if (response.ok) { + const familyMemberResponse = await response.json(); + console.log(familyMemberResponse); + return fetch(`http://localhost:5000/api/tree-info/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + object: [{ + "id": familyMemberResponse.member, + "data": { + "first name": data.firstname, + "last name": data.lastname, + }, + "rels": { + "children": [], + "spouses": [], + } + }], + userId: accountID, + }), + }); + }}) + } + else { + const errorData = await response.json(); + console.error('Error registering account:', errorData); + throw new Error('Account registration failed'); + } + }) + .then(async (response) => { + if (response.ok) { + const responseData = await response.json(); + console.log(responseData); + window.location.href = '/'; + } else { + const errorData = await response.json(); + console.error('Error initializing family member:', errorData); + } + }) + .catch((error) => { + console.error('Error:', error); + }); + }; const ButtonStyle = { fontFamily: 'Alata', @@ -101,14 +147,6 @@ const CreateAccount = () => { KinTree Logo

Create Account

- - {/* Error Message Display */} - {errorMessage && ( -
- {errorMessage} -
- )} -
@@ -168,24 +206,12 @@ const CreateAccount = () => {
-
-
-

- Already have an account? - - Login here - -

-
-
diff --git a/client/src/pages/Home/Home.js b/client/src/pages/Home/Home.js index 95898a9..0e90317 100644 --- a/client/src/pages/Home/Home.js +++ b/client/src/pages/Home/Home.js @@ -8,7 +8,7 @@ import CreateEventPopup from '../../components/CreateEvent/CreateEvent'; import CreateMemoryPopup from '../../components/CreateMemory/CreateMemory'; import NavBar from '../../components/NavBar/NavBar'; -function Home() { +function Home() { document.body.style.overflow = 'hidden'; document.body.style.width = '100%'; return ( diff --git a/client/src/pages/Login/Login.js b/client/src/pages/Login/Login.js index 21ff37d..c508f38 100644 --- a/client/src/pages/Login/Login.js +++ b/client/src/pages/Login/Login.js @@ -4,83 +4,47 @@ import logo from '../../assets/kintreelogo-adobe.png'; import { useForm } from 'react-hook-form'; import { Link } from 'react-router-dom'; import { useCurrentUser } from '../../CurrentUserProvider'; -import { handleLogin, handleSignInWithGoogle } from '../../utils/authHandlers'; -import { supabase } from '../../utils/supabaseClient'; function Login() { const { register, handleSubmit } = useForm(); const [ errorMessage, setErrorMessage ] = useState(""); - const [ needsConfirm, setNeedsConfirm ] = useState(false); - const [ attemptedEmail, setAttemptedEmail ] = useState(""); - const [ resendLoading, setResendLoading ] = useState(false); const { setCurrentAccountID, fetchCurrentUserID, fetchCurrentAccountID } = useCurrentUser(); - const [ mfaStep, setMfaStep ] = useState(false); - const [ mfaFactorId, setMfaFactorId ] = useState(""); - const [ mfaChallengeId, setMfaChallengeId ] = useState(""); - const [ mfaCode, setMfaCode ] = useState(""); - const [ mfaError, setMfaError ] = useState(""); - const onSubmit = async (data) => { - setErrorMessage(""); // clear previous errors - setNeedsConfirm(false); - setAttemptedEmail(data.email); - try { - await handleLogin(data.email, data.password); // password step - // After password login, check for verified TOTP factor - const { data: factorsData, error: lfErr } = await supabase.auth.mfa.listFactors(); - if (lfErr) throw lfErr; - const totp = factorsData?.all?.find(f => f.factor_type === 'totp' && f.status === 'verified'); - if (totp) { - const { data: challengeData, error: chErr } = await supabase.auth.mfa.challenge({ factorId: totp.id }); - if (chErr) throw chErr; - setMfaFactorId(totp.id); - setMfaChallengeId(challengeData?.id || ""); - setMfaStep(true); - return; // wait for MFA verify - } - // No MFA required → proceed - window.location.href = '/'; - } catch (error) { - const msg = String(error?.message || '').toLowerCase(); - const requiresConfirm = msg.includes('confirm') || msg.includes('not confirmed'); - if (requiresConfirm) { - setNeedsConfirm(true); - } else { - setErrorMessage(error.message); - } - } - }; - - const onSubmitMfa = async (e) => { - e.preventDefault(); - setMfaError(""); - try { - const { error } = await supabase.auth.mfa.verify({ factorId: mfaFactorId, challengeId: mfaChallengeId, code: mfaCode }); - if (error) throw error; - window.location.href = '/'; - } catch (e2) { - setMfaError(e2.message || 'Verification failed'); - } - } - - const handleResendConfirmation = async () => { - if (!attemptedEmail) return; - setResendLoading(true); - try { - const { error } = await supabase.auth.resend({ - type: 'signup', - email: attemptedEmail, - options: { emailRedirectTo: `${window.location.origin}/login` } - }); - if (error) throw error; - // surface a lightweight notice - setErrorMessage('Confirmation email sent. Please check your inbox.'); - } catch (e) { - setErrorMessage(e.message); - } finally { - setResendLoading(false); - } - } + const onSubmit = (data) => { + const requestOptions = { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }; + fetch('http://localhost:5000/api/auth/login', requestOptions) + .then(async(response) => { + if (response.ok) { + fetch(`http://localhost:5000/api/auth/user/email/${data.email}`, { + method: 'GET', + headers: { 'Content-Type': 'application/json' } + }) + .then(async(response) => { + if (response.ok) { + let userData = await response.json(); + await setCurrentAccountID(userData.id); // set the current user ID in context + console.log("set currentAccountID to: ", userData.id); + await fetchCurrentUserID(); + window.location.href='/' + } + }) + return response.json(); + } + else { + const errorData = await response.json(); + console.error('Error:', errorData.message); + setErrorMessage(errorData.message); + throw new Error('Network response was not ok'); + } + }) + .catch(error => { + console.error('There was a problem with the fetch operation:', error); + }) + }; document.body.style.overflow = 'hidden'; document.body.style.width = '100%'; @@ -90,24 +54,7 @@ function Login() {
KinTree Logo

Sign In

- {!mfaStep && ( -
- {needsConfirm && ( -
-
Please confirm your email to continue. We sent a link to
{attemptedEmail}
- -
- )} + onSubmit(data))} style={styles.FormStyle}>
) diff --git a/client/src/pages/Reset/Reset.js b/client/src/pages/Reset/Reset.js index cb22a75..986f727 100644 --- a/client/src/pages/Reset/Reset.js +++ b/client/src/pages/Reset/Reset.js @@ -1,56 +1,12 @@ -import { useState } from "react"; -import { handleResetPassword } from '../../utils/authHandlers'; -import * as styles from '../Login/styles'; -import logo from '../../assets/kintreelogo-adobe.png'; +import React from 'react'; +import * as styles from './styles'; -export default function ResetPassword() { - const [email, setEmail] = useState(""); - const [message, setMessage] = useState(""); +function Reset() { + return ( +
- const onSubmit = async (e) => { - e.preventDefault(); - try { - await handleResetPassword(email); - setMessage('Check your email for a reset link.'); - } catch (error) { - // message handled by handler alert - } - }; - - return ( -
-
- KinTree Logo -

Reset Password

-
- {message && ( -
- {message} -
- )} -
    -
  • - - setEmail(e.target.value)} - style={styles.FieldStyle} - required - /> -
  • -
-
- -
-
-

- Remembered your password? Back to Sign In -

-
-
-
-
- ); +
+ ) } + +export default Reset; \ No newline at end of file diff --git a/client/src/pages/SyncContacts/SyncContacts.js b/client/src/pages/SyncContacts/SyncContacts.js new file mode 100644 index 0000000..eb12fcf --- /dev/null +++ b/client/src/pages/SyncContacts/SyncContacts.js @@ -0,0 +1,67 @@ +import { React, useState } from 'react'; +import * as styles from './styles'; + +function SyncContacts() { + const [contacts, setContacts] = useState([]); + const [errorMessage, setErrorMessage] = useState(""); + const [loading, setLoading] = useState(false); + + const handleSyncContacts = () => { + setLoading(true); + + fetch('http://localhost:5000/api/contacts/sync', { + method: 'GET', + headers: { 'Content-Type': 'application/json' } + }) + .then(async (response) => { + if (response.ok) { + const data = await response.json(); + setContacts(data); + console.log("Contacts synced:", data); + } else { + const errorData = await response.json(); + console.error('Error:', errorData.message); + setErrorMessage(errorData.message); + } + }) + .catch(error => { + console.error('There was a problem with the fetch operation:', error); + setErrorMessage("Failed to sync contacts"); + }) + .finally(() => { + setLoading(false); + }); + }; + + return ( +
+

Sync Contacts

+ + {/* Sync Button */} + + + {/* Error Message */} + {errorMessage && ( +

+ {errorMessage} +

+ )} + + {/* Contacts List */} +
    + {contacts.map((contact, index) => ( +
  • + {contact.name} - {contact.email} +
  • + ))} +
+
+ ); +} + +export default SyncContacts; \ No newline at end of file diff --git a/client/src/pages/SyncContacts/SyncContatcs.css b/client/src/pages/SyncContacts/SyncContatcs.css new file mode 100644 index 0000000..8c5a989 --- /dev/null +++ b/client/src/pages/SyncContacts/SyncContatcs.css @@ -0,0 +1,5 @@ +.home-page { + display: flex; + text-align: center; + color: black; +} \ No newline at end of file diff --git a/client/src/pages/SyncContacts/styles.js b/client/src/pages/SyncContacts/styles.js new file mode 100644 index 0000000..470ea5b --- /dev/null +++ b/client/src/pages/SyncContacts/styles.js @@ -0,0 +1,39 @@ +// Sync Contacts Styles + +export const SyncContainer = { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + marginTop: '20px', + width: '100%' +}; + +export const SyncButton = { + padding: '10px 20px', + marginTop: '10px', + borderRadius: '8px', + border: 'none', + backgroundColor: '#bbbbbb', + color: 'white', + cursor: 'pointer' +}; + +export const SyncList = { + marginTop: '20px', + width: '80%', + listStyleType: 'none', + padding: '0' +}; + +export const SyncItem = { + padding: '10px', + borderBottom: '1px solid #000000', + textAlign: 'left', + width: '100%' +}; + +export const ErrorText = { + color: 'red', + marginTop: '10px', + textAlign: 'center' +}; \ No newline at end of file diff --git a/client/src/pages/WebsiteSettings/WebsiteSettings.js b/client/src/pages/WebsiteSettings/WebsiteSettings.js index a9936b2..a229e53 100644 --- a/client/src/pages/WebsiteSettings/WebsiteSettings.js +++ b/client/src/pages/WebsiteSettings/WebsiteSettings.js @@ -1,144 +1,11 @@ -import { useState, useEffect } from "react"; +import { useState } from "react"; import * as styles from "./styles"; import NavBar from "../../components/NavBar/NavBar"; -import { handleLogout } from '../../utils/authHandlers'; -import { supabase } from '../../utils/supabaseClient'; function WebsiteSettings() { const [notifications, setNotifications] = useState(true); const [darkMode, setDarkMode] = useState(false); - const [totpFactorId, setTotpFactorId] = useState(""); - const [totpQr, setTotpQr] = useState(""); - const [totpCode, setTotpCode] = useState(""); - const [totpStatus, setTotpStatus] = useState(""); - const [totpLoading, setTotpLoading] = useState(false); - const [totpVerified, setTotpVerified] = useState(false); - async function loadFactors() { - try { - const { data: factorsData, error } = await supabase.auth.mfa.listFactors(); - if (error) throw error; - const verified = factorsData?.all?.find(f => f.factor_type === 'totp' && f.status === 'verified'); - const unverified = factorsData?.all?.find(f => f.factor_type === 'totp' && f.status === 'unverified'); - if (verified) { - setTotpVerified(true); - setTotpFactorId(verified.id); - setTotpQr(""); - } else if (unverified) { - setTotpVerified(false); - setTotpFactorId(unverified.id); - setTotpQr(""); // we can't re-fetch QR; allow verify via code - } else { - setTotpVerified(false); - setTotpFactorId(""); - setTotpQr(""); - } - } catch (e) { - console.error('Load factors error:', e); - } - } - - useEffect(() => { - loadFactors(); - }, []); - - async function startTotpEnroll() { - setTotpStatus(""); - setTotpLoading(true); - try { - // Avoid starting a new enroll while one is pending - if (totpVerified) { - setTotpStatus('Two-factor authentication is already enabled.'); - return; - } - if (totpFactorId && !totpVerified) { - setTotpStatus('A TOTP setup is pending. Enter a code from your authenticator, or click Start over.'); - return; - } - const { data, error } = await supabase.auth.mfa.enroll({ factorType: 'totp' }); - if (error) throw error; - console.log('Enroll data:', data); - setTotpFactorId(data.id); - setTotpQr(data.totp?.qr_code || ""); - } catch (e) { - setTotpStatus(e.message); - } finally { - setTotpLoading(false); - } - } - - async function verifyTotp() { - if (!totpFactorId || !totpCode) return; - setTotpLoading(true); - setTotpStatus(""); - try { - // Ensure we have the correct pending factorId in case state was lost - if (!totpFactorId) { - const { data: factorsData, error: factorsErr } = await supabase.auth.mfa.listFactors(); - if (factorsErr) throw factorsErr; - console.log('Factors:', factorsData); - const pending = factorsData?.all?.find(f => f.factor_type === 'totp' && f.status === 'unverified'); - if (pending) setTotpFactorId(pending.id); - } else { - const { data: factorsData, error: factorsErr } = await supabase.auth.mfa.listFactors(); - if (!factorsErr) console.log('Factors:', factorsData); - } - - console.log('Using factorId:', totpFactorId, 'Code:', totpCode); - // Create a challenge, then verify with challengeId (works across SDK versions) - const { data: challengeData, error: challengeErr } = await supabase.auth.mfa.challenge({ factorId: totpFactorId }); - if (challengeErr) throw challengeErr; - console.log('Challenge data:', challengeData); - const challengeId = challengeData?.id; - const { error } = await supabase.auth.mfa.verify({ factorId: totpFactorId, challengeId, code: totpCode }); - if (error) throw error; - setTotpStatus('Two-factor authentication enabled.'); - setTotpQr(""); - setTotpCode(""); - setTotpVerified(true); - } catch (e) { - console.error('TOTP verify error:', e); - setTotpStatus(e.message || 'Verification failed'); - } finally { - setTotpLoading(false); - } - } - - async function disableTotp() { - if (!totpFactorId) return; - setTotpLoading(true); - setTotpStatus(""); - try { - const { error } = await supabase.auth.mfa.unenroll({ factorId: totpFactorId }); - if (error) throw error; - setTotpVerified(false); - setTotpFactorId(""); - setTotpStatus('Two-factor authentication disabled.'); - } catch (e) { - setTotpStatus(e.message || 'Failed to disable'); - } finally { - setTotpLoading(false); - } - } - - async function restartTotpEnroll() { - // For lingering unverified factor: unenroll then start fresh - if (totpFactorId && !totpVerified) { - try { - const { error } = await supabase.auth.mfa.unenroll({ factorId: totpFactorId }); - if (error) throw error; - setTotpFactorId(""); - setTotpQr(""); - setTotpCode(""); - setTotpStatus('Previous pending setup cleared.'); - } catch (e) { - setTotpStatus(e.message || 'Could not reset existing setup'); - return; - } - } - await startTotpEnroll(); - } - return (
@@ -163,64 +30,13 @@ function WebsiteSettings() {

-
- - {totpVerified && ( -
- Enabled - - {totpStatus && {totpStatus}} -
- )} - {!totpVerified && !totpQr && !totpFactorId && ( -
- - {totpStatus && {totpStatus}} -
- )} - {!totpVerified && totpFactorId && !totpQr && ( -
-
Enter a 6‑digit code from your authenticator to complete setup.
- setTotpCode(e.target.value)} - /> -
- - -
- {totpStatus && {totpStatus}} -
- )} - {!totpVerified && totpQr && ( -
-
Scan this QR with Duo/Google Authenticator, then enter the 6‑digit code:
- TOTP QR - setTotpCode(e.target.value)} - /> - - {totpStatus && {totpStatus}} -
- )} -
+ + + setNotifications(!notifications)} + />
{/* Profile & Personalization */} @@ -251,15 +67,6 @@ function WebsiteSettings() {
- -
- -
diff --git a/client/src/pages/WebsiteSettings/styles.js b/client/src/pages/WebsiteSettings/styles.js index 6aa3b72..5a08a9b 100644 --- a/client/src/pages/WebsiteSettings/styles.js +++ b/client/src/pages/WebsiteSettings/styles.js @@ -63,31 +63,4 @@ export const Input = { export const ToggleSwitch = { marginLeft: '10px', transform: 'scale(1.2)' -}; - -export const SignOutContainer = { - display: 'flex', - justifyContent: 'flex-end', - padding: '20px', - marginTop: '30px', - borderTop: '1px solid #e0e0e0' -}; - -export const SignOutButton = { - backgroundColor: '#dc3545', - color: 'white', - border: 'none', - borderRadius: '8px', - padding: '12px 24px', - fontSize: '16px', - fontWeight: '600', - cursor: 'pointer', - boxShadow: '0 2px 4px rgba(220, 53, 69, 0.2)', - transition: 'all 0.2s ease', - minWidth: '120px' -}; - -export const SignOutButtonHover = { - backgroundColor: '#c82333', - boxShadow: '0 4px 8px rgba(220, 53, 69, 0.3)' }; \ No newline at end of file diff --git a/docs/.env.example b/docs/.env.example index f25ad8d..2814bb3 100644 --- a/docs/.env.example +++ b/docs/.env.example @@ -1,7 +1,5 @@ -# SERVER ENV -SUPABASE_URL= -SUPABASE_SERVICE_ROLE_KEY= - -# CLIENT ENV -REACT_APP_SUPABASE_URL= -REACT_APP_SUPABASE_ANON_KEY= \ No newline at end of file +DB_HOST=localhost +DB_PORT=3306 +DB_USER=root +DB_PASSWORD=my_sql_password +DB_DATABASE=my_database_name diff --git a/server/controllers/authController.js b/server/controllers/authController.js index d161f0e..7962d04 100644 --- a/server/controllers/authController.js +++ b/server/controllers/authController.js @@ -1,20 +1,129 @@ -// authController.js - the main backend file for user registration, signin, etc -const User = require('../models/userModel'); // now backed by Supabase +// authController.js +const bcrypt = require('bcryptjs'); +const User = require('../models/userModel'); -const deleteByUser = async (req,res) => { - const { id } = req.params; +const register = async (req, res) => { + console.log('Regiater function called'); + try { + const { username, email, password } = req.body; + + if (!email || !password || !username) { + return res.status(400).json({ error: 'All fields are required' }); + } + + const existingUser = await User.findByEmail(email); + if (existingUser) return res.status(400).json({ + error: 'Email already in use' + }); + + const saltRounds = 12; + const salt = await bcrypt.genSalt(saltRounds); + const hashedPassword = await bcrypt.hash(password, salt); + + const [newUser] = await User.register({ + username, + email, + password: hashedPassword + }); + + res.status(201).json({ + message: 'User registered successfully', user: newUser + }); + } catch (error) { + console.error(error); + res.status(500).json({ + error: 'Registration failed' + }); + } +}; +const login = async(req,res) => { try{ + const { email, password } = req.body; + if(!email || !password){ + return res.status(400).json({ + message: 'Missing an email or password' + }); + } + const existingUser = await User.findByEmail(email); + if(!existingUser){ + return res.status(401).json({ + message: 'User is not found. Please register!' + }); + } + const passwordCompare = await bcrypt.compare(password, existingUser.password) + if(!passwordCompare){ + return res.status(401).json({ + message: "Invalid credentials" + }); + } + + res.status(200).json({ + message: "You are logged in!" + }); + } + catch (error){ + console.error(error); + res.status(500).json({ + error: 'Registration failed' + }); + + } +}; + +const editByUser = async (req, res) => { + try { + const { id } = req.params; + const { username, email, password } = req.body; + + const user = await User.findById(id); + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + const updatedFields = {}; + + if (username) updatedFields.username = username; + if (email) { + const existingUser = await User.findByEmail(email); + if (existingUser && existingUser.id !== parseInt(id)) { + return res.status(400).json({ error: 'Email already in use' }); + } + updatedFields.email = email; + } + if (password) { + const saltRounds = 12; + const salt = await bcrypt.genSalt(saltRounds); + const hashedPassword = await bcrypt.hash(password, salt); + updatedFields.password = hashedPassword; + } + + const updatedUser = await User.updateUser(id, updatedFields); + + res.status(200).json({ + message: 'User updated successfully', + user: updatedUser + }); + } catch (error) { + console.error(error); + res.status(500).json({ error: 'Error updating user' }); + } +}; + +async function deleteByUser(req, res) { + const { id } = req.params; + + try { await User.deleteUser(id); - res.json({ + res.json({ message: "User deleted successfullyS" - }) + }); } - catch (error){ + catch (error) { console.error(error); - res.status(500).json({error:"Error deleting user"}) + res.status(500); json({ error: "Error deleting user" }); } } @@ -56,26 +165,4 @@ const getAllUsers = async (req, res) => { } } -module.exports = { deleteByUser, findById, findByEmail, getAllUsers }; - -// Add a sync endpoint: POST /api/auth/sync -// Body: { auth_uid, email, username, firstName, lastName, phoneNumber, birthDate } -const syncAuthUser = async (req, res) => { - try { - const { auth_uid, email, username, firstName, lastName, phoneNumber, birthDate } = req.body || {}; - if (!auth_uid || !email) { - return res.status(400).json({ error: 'auth_uid and email are required' }); - } - const user = await User.upsertByAuthUser({ auth_uid, email, username, firstName, lastName, phoneNumber, birthDate }); - res.status(200).json(user); - } catch (error) { - console.error('Sync error:', error); - res.status(500).json({ - error: 'Error syncing auth user', - details: error.message, - stack: process.env.NODE_ENV === 'development' ? error.stack : undefined - }); - } -}; - -module.exports.syncAuthUser = syncAuthUser; +module.exports = { register,login, deleteByUser, findById, findByEmail, getAllUsers }; diff --git a/server/controllers/backupController.js b/server/controllers/backupController.js index 6a09648..b03ebc9 100644 --- a/server/controllers/backupController.js +++ b/server/controllers/backupController.js @@ -8,17 +8,13 @@ const backupInfo = require('../models/backupModel'); const backupUser = async (req, res) => { try{ const { id } = req.params; - // Resolve UUID to integer if needed - const User = require('../models/userModel'); - const userIdInt = await User.resolveUserIdFromAuthUid(id) || id; - - const user = await userInfo.findById(userIdInt); + const user = await userInfo.findById(id); if(!user) { - return res.status(404).json({ error: "User not found"}); + return { error: "User not found"}; } - const tree = await treeMember.getMembersByUser(userIdInt); - const relationships = await relationship.getRelationshipByUser(userIdInt); - const sharedTree = await sharedTrees.getSharedTreebySender(userIdInt); + const tree = await treeMember.getAllMembersbyId(id); + const relationships = await relationship.getRelationships(id); + const sharedTree = await sharedTrees.getSharedTreebySender(id); const data = { user, @@ -27,7 +23,7 @@ const backupUser = async (req, res) => { sharedTree }; - await backupInfo.addBackup(userIdInt, JSON.stringify(data)); + await backupInfo.addBackup(id, JSON.stringify(data)); res.json({ message: 'Backup completed' }); @@ -35,8 +31,7 @@ const backupUser = async (req, res) => { catch (error){ console.error(error); res.status(500).json({ - error: 'Error backing up data', - details: error.message + error: 'Error backing up data' }); } }; @@ -44,25 +39,21 @@ const backupUser = async (req, res) => { const restoreUser = async (req, res) => { try{ const {id} = req.params; - // Resolve UUID to integer if needed - const User = require('../models/userModel'); - const userIdInt = await User.resolveUserIdFromAuthUid(id) || id; - - const existingUser = await userInfo.findById(userIdInt); + const existingUser = await userInfo.findById(id); if(!existingUser){ return res.status(404).json({ error: "User not found. No data to restore" }) } - const backup = await backupInfo.getLatestBackup(userIdInt); + const backup = await backupInfo.getLatestBackup(id); if(!backup) { return res.status(404).json({ error: "No backup available" }) } - const backupData = JSON.parse(backup.backupdata || backup.backupData); + const backupData = backup.backupData for( const member of backupData.tree) { const exists= await treeMember.getMemberById(member.id) diff --git a/server/controllers/contactController.js b/server/controllers/contactController.js new file mode 100644 index 0000000..395dde2 --- /dev/null +++ b/server/controllers/contactController.js @@ -0,0 +1,36 @@ +const Contact = require("../models/Contacts"); + + +exports.syncContacts = async (req, res) => { + try { + const userId = req.user.id; + const { contacts } = req.body; + + if (!contacts || !Array.isArray(contacts)) { + return res.status(400).json({ + message: "Contacts must be an array" + }); + } + + // Attach user_id to each contact before saving + const formattedContacts = contacts.map((c) => ({ + user_id: userId, + name: c.name, + phone: c.phone, + email: c.email || null, + relationship: c.relationship || null + })); + + const result = await Contact.create(formattedContacts); + + res.status(200).json({ + message: "Contacts synced successfully", + data: result + }); + } catch (error) { + res.status(500).json({ + message: "Error syncing contacts", + error: error.message + }); + } +}; \ No newline at end of file diff --git a/server/controllers/relationshipController.js b/server/controllers/relationshipController.js index 81db440..e5b1520 100644 --- a/server/controllers/relationshipController.js +++ b/server/controllers/relationshipController.js @@ -1,6 +1,4 @@ const Relationship = require('../models/relationshipModel'); -const User = require('../models/userModel'); -const treeMember = require('../models/treeMemberModel'); const getRelationships = async (req, res) => { try{ @@ -45,69 +43,28 @@ const getRelationshipsByOtherUser = async (req,res) => { }; const addRelationship = async (req,res) =>{ + //need to add functionality to refuse a relationship if it already exists - try{ - let {person1_id, person2_id, relationshipType, relationshipStatus, side, userId} = req.body; - - console.log('addRelationship received:', {person1_id, person2_id, userId, typeof_person1_id: typeof person1_id}); - - // Resolve person1_id if it's a UUID (user ID) - need to find the member ID for that user - if (typeof person1_id === 'string' && person1_id.includes('-')) { - const userIdInt = await User.resolveUserIdFromAuthUid(person1_id); - if (!userIdInt) { - return res.status(400).json({ error: 'Invalid person1_id: User not found' }); - } - // Find the active member for this user - const member = await treeMember.getActiveMemberId(userIdInt); - if (!member) { - return res.status(400).json({ error: 'No active member found for person1_id user' }); - } - person1_id = member.id; - } - - // Resolve person2_id if it's a UUID (user ID) - if (typeof person2_id === 'string' && person2_id.includes('-')) { - const userIdInt = await User.resolveUserIdFromAuthUid(person2_id); - if (!userIdInt) { - return res.status(400).json({ error: 'Invalid person2_id: User not found' }); - } - // Find the active member for this user - const member = await treeMember.getActiveMemberId(userIdInt); - if (!member) { - return res.status(400).json({ error: 'No active member found for person2_id user' }); - } - person2_id = member.id; - } - - // Resolve userId from UUID to integer - if (userId && typeof userId === 'string' && userId.includes('-')) { - userId = await User.resolveUserIdFromAuthUid(userId); - if (!userId) { - return res.status(400).json({ error: 'Invalid userId: User not found' }); - } - } - - console.log('addRelationship resolved:', {person1_id, person2_id, userId}); - - const newRelationship = await Relationship.addRelationship({ - person1_id, - person2_id, - relationshipType, - relationshipStatus, - side, - userId - }); - res.status(201).json({ - message: 'Relationship added successfully', - member: newRelationship - }); - } catch (error) { - console.error(error); - res.status(500).json({ - error: 'Error adding relationship', - details: error.message - }); - } +try{ + const {person1_id, person2_id, relationshipType, relationshipStatus, side, userId} = req.body; + const [newRelationship] = await Relationship.addRelationship({ + person1_id, + person2_id, + relationshipType, + relationshipStatus, + side, + userId + }); + res.status(201).json({ + message: 'Relationship added successfully', + member: newRelationship + }); +} catch (error) { + console.error(error); + res.status(500).json({ + error: 'Error adding relationship' + }); +} } const filterBySide = async (req,res) => { diff --git a/server/controllers/sharedTreeController.js b/server/controllers/sharedTreeController.js index 0f06de3..2d0e2c9 100644 --- a/server/controllers/sharedTreeController.js +++ b/server/controllers/sharedTreeController.js @@ -48,8 +48,8 @@ const getSharedTreeByToken = async (req, res) => { const getSharedTreeBySender = async (req, res) => { try{ const { id} = req.params; - const trees = await sharedTrees.getSharedTreebySender(id); - res.status(200).json(trees); + const relationships = await sharedTrees.getSharedTreebySender(id); + res.status(200).json(sharedTrees); } catch(error){ console.error(error); diff --git a/server/controllers/treeInfoController.js b/server/controllers/treeInfoController.js index bd66866..a55195d 100644 --- a/server/controllers/treeInfoController.js +++ b/server/controllers/treeInfoController.js @@ -1,15 +1,12 @@ const treeInfo = require('../models/treeInfoModel'); -const User = require('../models/userModel'); const addObject = async (req, res) => { try { const { object, userId } = req.body; - // Resolve UUID to integer user ID if needed - const userIdInt = await User.resolveUserIdFromAuthUid(userId) || userId; - const newObject = await treeInfo.addObject({ + const [newObject] = await treeInfo.addObject({ object: JSON.stringify(object), - userId: userIdInt + userId: userId }); res.status(201).json({ @@ -66,12 +63,8 @@ const updateObject = async (req, res) => { const getObject = async (req, res) => { try { const { id } = req.params; - // Resolve UUID to integer user ID first - const userId = await User.resolveUserIdFromAuthUid(id); - if (!userId) { - return res.status(404).json({ error: 'User not found' }); - } - const retrievedObject = await treeInfo.getObject(userId); + + const retrievedObject = await treeInfo.getObject(id); if (!retrievedObject) { return res.status(404).json({ error: 'Object not found' @@ -84,7 +77,6 @@ const getObject = async (req, res) => { console.error(error); res.status(500).json({ error: 'Error retrieving tree object', - details: error.message }); } } diff --git a/server/controllers/treeMemberController.js b/server/controllers/treeMemberController.js index c4af765..1ecaa08 100644 --- a/server/controllers/treeMemberController.js +++ b/server/controllers/treeMemberController.js @@ -1,62 +1,30 @@ const treeMember = require('../models/treeMemberModel'); const relationship = require('../models/relationshipModel'); -const User = require('../models/userModel'); const addTreeMember = async (req, res) => { try { const { firstName, lastName, birthDate, deathDate, location, phoneNumber, relationships, userId, memberUserId } = req.body; - // Resolve UUIDs to integer user IDs - CRITICAL: database requires integers, not UUIDs - console.log('addTreeMember received userId:', userId, typeof userId); - const userIdInt = await User.resolveUserIdFromAuthUid(userId); - console.log('Resolved userIdInt:', userIdInt, typeof userIdInt); - if (!userIdInt) { - return res.status(400).json({ - error: 'Invalid user ID. User not found in database. Please sync your account first.', - received: userId - }); - } - - const memberUserIdInt = memberUserId ? await User.resolveUserIdFromAuthUid(memberUserId) : null; - if (memberUserId && !memberUserIdInt) { - return res.status(400).json({ - error: 'Invalid member user ID. User not found in database.', - received: memberUserId - }); - } - // ensure all necessary fields are passed in the request body - const newMember = await treeMember.addMember({ + const [newMember] = await treeMember.addMember({ firstName, lastName, birthDate, deathDate, location, phoneNumber, - userId: userIdInt, // Now guaranteed to be an integer - memberUserId: memberUserIdInt // Now guaranteed to be an integer or null + userId, + memberUserId }); /// need to fix that a value can be left empty (deathDate) // if there are relationships, add them to the database if (relationships && relationships.length > 0) { for (const rel of relationships) { - // Ensure person2_id is an integer, not a UUID - let person2_id = rel.person2_id; - if (typeof person2_id === 'string' && person2_id.includes('-')) { - // If it looks like a UUID, try to resolve it - person2_id = await User.resolveUserIdFromAuthUid(person2_id); - if (!person2_id) { - console.error('Could not resolve person2_id UUID:', rel.person2_id); - continue; // Skip this relationship - } - } await relationship.addRelationship({ person1_id: newMember.id, - person2_id: person2_id, - relationshipType: rel.relationshipType || 'sibling', - relationshipStatus: 'active', - userId: userIdInt // Need to include userId for the relationship + person2_id: rel.person2_id, // Corrected from 'relationship.person2_id' to 'rel.person2_id' + relationship_status: 'active' }); } } @@ -117,21 +85,16 @@ const editTreeMember = async (req, res) => { const getMembersByUser = async (req,res) =>{ try{ + const { userId } = req.params; - // Resolve UUID to integer user ID first - const userIdInt = await User.resolveUserIdFromAuthUid(userId); - if (!userIdInt) { - return res.status(404).json({ error: 'User not found' }); - } - const members = await treeMember.getMembersByUser(userIdInt) + const members = await treeMember.getMembersByUser(userId) console.log(members); res.status(200).json(members); } catch(error){ console.error(error); res.status(500).json({ - error: 'Error fetching members', - details: error.message + error: 'Error fetching members' }); } @@ -140,7 +103,7 @@ const getMembersByUser = async (req,res) =>{ const getMembersByOtherUser = async (req,res) =>{ try{ const { userId} = req.params; - const members = await treeMember.getMembeByOtherUser(userId) + const members = await treeMember.getMembersByOtherUser(userId) res.status(200).json(members); } catch(error){ @@ -166,7 +129,7 @@ const deleteByUser = async (req, res) => { } catch (error){ console.error(error); - res.status(500).json({error:"Error deleting family member"}) + res.status(500);json({error:"Error deleting family member"}) } } @@ -187,18 +150,14 @@ const getMemberById = async (req, res) => { const getActiveMemberId = async (req, res) => { try { const { id } = req.params; - // Resolve UUID to integer user ID first - const userId = await User.resolveUserIdFromAuthUid(id); - if (!userId) { - return res.status(200).json({}); + const member = await treeMember.getActiveMemberId(id); + if (!member) { + return res.status(404).json({ error: 'Family member not found' }); } - const member = await treeMember.getActiveMemberId(userId); - // If none found, return empty object to avoid frontend JSON parse errors - if (!member) return res.status(200).json({}); res.status(200).json(member); } catch (error) { console.error(error); - res.status(500).json({ error: 'Error fetching family member', details: error.message }); + res.status(500).json({ error: 'Error fetching family member' }); } } diff --git a/server/controllers/treeSummaryController.js b/server/controllers/treeSummaryController.js index f09611f..e07320d 100644 --- a/server/controllers/treeSummaryController.js +++ b/server/controllers/treeSummaryController.js @@ -8,22 +8,18 @@ const treeSummary = require('../models/treeSummaryModel'); const updateUserTreeSummary = async (req, res) => { const { userId } = req.params; try { - // Resolve UUID to integer if needed - const User = require('../models/userModel'); - const userIdInt = await User.resolveUserIdFromAuthUid(userId) || userId; - - const members = await treeMember.getMembersByUser(userIdInt); - const relationships = await relationship.getRelationshipByUser(userIdInt); + const members = await treeMember.getMemberByUser(userId); + const relationships = await relationship.getRelationshipByUser(userId); const summary = { members, relationships}; - const existing = await treeSummary.getSummaryByUser(userIdInt); + const existing = treeSummary.getSummaryByUser(userId); if(existing){ - await treeSummary.updateSummary(userIdInt, summary); + await treeSummary.updateSummary(userId, summary); } else{ - await treeSummary.createSummary(userIdInt, summary); + await treeSummary.createSummary(userId,summary) } res.json({ message: 'Tree summary updated' @@ -32,8 +28,7 @@ const updateUserTreeSummary = async (req, res) => { catch (error) { console.error(error); res.status(500).json({ - error: 'Failed to update tree summary', - details: error.message + error: 'Failed to update tree summary' }); } diff --git a/server/db/knex.js b/server/db/knex.js new file mode 100644 index 0000000..cb9e3d3 --- /dev/null +++ b/server/db/knex.js @@ -0,0 +1,7 @@ +require('dotenv').config() +const knex = require('knex'); +const config = require('../knexfile.js'); + +const db = knex(config.development); + +module.exports = db; diff --git a/server/knexfile.js b/server/knexfile.js new file mode 100644 index 0000000..d49ce6a --- /dev/null +++ b/server/knexfile.js @@ -0,0 +1,23 @@ +require('dotenv').config(); + +module.exports = { + development: { + client: 'mysql2', + connection: { + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_DATABASE, + port: process.env.DB_PORT || 3306 + }, + migrations: { + directory: './migrations' + }, + seeds: { + directory: './seeds' + } + } +}; + + + diff --git a/server/migrations/20250416174536_add_user_tree_table.js b/server/migrations/20250416174536_add_user_tree_table.js index 4d611a2..80a2584 100644 --- a/server/migrations/20250416174536_add_user_tree_table.js +++ b/server/migrations/20250416174536_add_user_tree_table.js @@ -19,5 +19,5 @@ exports.up = function(knex) { */ exports.down = function(knex) { return knex.schema.dropTableIfExists('userTreeSummaries') - + }; diff --git a/server/models/Contacts.js b/server/models/Contacts.js new file mode 100644 index 0000000..caa326a --- /dev/null +++ b/server/models/Contacts.js @@ -0,0 +1,45 @@ +const db = require("../db/knex"); + +const Contacts = { + + addContact: async (data) => { + return db("contacts").insert(data); + }, + + addMultipleContacts: async (contacts) => { + return db("contacts").insert(contacts); + }, + + getContactsByUser: async (userId) => { + return db("contacts") + .where("user_id", userId) + .select("*"); + }, + + getContactById: async (id) => { + return db("contacts") + .where("id", id) + .first(); + }, + + updateContact: async (id, data) => { + return db("contacts") + .where("id", id) + .update(data); + }, + + deleteContact: async (id) => { + return db("contacts") + .where("id", id) + .del(); + }, + + deleteByUser: async (userId) => { + return db("contacts") + .where("user_id", userId) + .del(); + } + +}; + +module.exports = Contacts; \ No newline at end of file diff --git a/server/models/backupModel.js b/server/models/backupModel.js index 6c3dd16..506837b 100644 --- a/server/models/backupModel.js +++ b/server/models/backupModel.js @@ -1,48 +1,14 @@ -// backupModel.js - model for backups table (Supabase) -const supabase = require('../lib/supabase'); +const db = require('../db/knex'); const backup = { - addBackup: async(userId, data) => { - // Resolve UUID to integer if needed - let userIdInt = userId; - if (typeof userId === 'string' && userId.includes('-')) { - const User = require('./userModel'); - userIdInt = await User.resolveUserIdFromAuthUid(userId); - if (!userIdInt) throw new Error('User not found'); - } - const { data: inserted, error } = await supabase - .from('backups') - .insert([{ userid: userIdInt, backupdata: data }]) - .select('*') - .single(); - if (error) throw error; - return inserted; + addBackup: async(user, data) => { + return db('backups').insert({'userId': user, 'backupData': data}); }, getBackups: async (id) => { - const { data, error } = await supabase - .from('backups') - .select('*') - .eq('backupid', id); - if (error) throw error; - return data; + return db('backups').where({id}); }, - getLatestBackup: async (userId) => { - // Resolve UUID to integer if needed - let userIdInt = userId; - if (typeof userId === 'string' && userId.includes('-')) { - const User = require('./userModel'); - userIdInt = await User.resolveUserIdFromAuthUid(userId); - if (!userIdInt) throw new Error('User not found'); - } - const { data, error } = await supabase - .from('backups') - .select('*') - .eq('userid', userIdInt) - .order('createdat', { ascending: false }) - .limit(1) - .maybeSingle(); - if (error) throw error; - return data; + getLatestBackup: async (id) => { + return db('backups').where('backupId', id).orderBy('createdAt', 'desc').first(); } }; diff --git a/server/models/relationshipModel.js b/server/models/relationshipModel.js index 5f03682..cc8dd89 100644 --- a/server/models/relationshipModel.js +++ b/server/models/relationshipModel.js @@ -1,85 +1,36 @@ -// relationshipModel.js - the model for the relationships table -// this file was replaced with the supabase model -const supabase = require('../lib/supabase'); +const db = require('../db/knex'); -// all functions for the relationship to interact with the database const Relationships = { addRelationship: async (data) => { - // Map camelCase to lowercase column names for Postgres - const mappedData = { - person1_id: data.person1_id || data.person1Id, - person2_id: data.person2_id || data.person2Id, - relationshiptype: data.relationshipType || data.relationshiptype, - relationshipstatus: data.relationshipStatus || data.relationshipstatus, - side: data.side, - userid: data.userId || data.userid, - }; - // Remove undefined/null values - Object.keys(mappedData).forEach(key => mappedData[key] === undefined && delete mappedData[key]); - const { data: inserted, error } = await supabase - .from('relationships') - .insert([ mappedData ]) - .select('*') - .single(); - if (error) throw error; - return inserted; + return db('relationships').insert(data); }, - getRelationships: async (personId) => { - // person1_id = personId OR person2_id = personId - const { data, error } = await supabase - .from('relationships') - .select('*') - .or(`person1_id.eq.${personId},person2_id.eq.${personId}`); - if (error) throw error; - return data; + getRelationships:async (personId) => { + return db('relationships').where('person1_id', personId).orWhere('person2_id', personId); }, - filterBySide: async (personId, side) => { - const { data, error } = await supabase - .from('relationships') - .select('*') - .eq('person1_id', personId) - .eq('side', side); - if (error) throw error; - return data; + filterBySide: async(personId, side) => { + return db('relationships').where('person1_id', personId).andWhere('side',side); }, getRelationshipbyId: async (personId) => { - const { data, error } = await supabase - .from('relationships') - .select('*') - .eq('person1_id', personId) - .eq('person2_id', personId); - if (error) throw error; - return data; + return db('relationships').where('person1_id', personId).andWhere('person2_id', personId); }, getRelationshipByUser: async (userId) => { - const { data, error } = await supabase - .from('relationships') - .select('*') - .eq('userid', userId); - if (error) throw error; - return data; + return db('relationship').where('userId', userId).select('*'); }, getRelationshipByOtherUser: async (userId) => { - const { data, error } = await supabase - .from('relationships') - .select('*') - .not('userid', 'eq', userId); - if (error) throw error; - return data; + return db('relationship').whereNot('userId', userId).select('*'); }, deleteByUser: async (userId) => { - const { error } = await supabase - .from('relationships') - .delete() - .eq('userid', userId); - if (error) throw error; + return db('relationship').where({userId}).del(); } + + + }; module.exports = Relationships; diff --git a/server/models/sharedTreeModel.js b/server/models/sharedTreeModel.js index 8c128f8..6ac65ec 100644 --- a/server/models/sharedTreeModel.js +++ b/server/models/sharedTreeModel.js @@ -1,65 +1,53 @@ -// sharedTreeModel.js - the model for the sharedTrees table -// this file was replaced with the supabase model -const supabase = require('../lib/supabase'); +const db = require('../db/knex'); +const Relationships = require('./relationshipModel'); -// all functions for the sharedTree to interact with the database -const sharedTrees = { - addSharedTree: async (data) => { - // Table and column names are lowercase in Postgres - const { data: inserted, error } = await supabase - .from('sharedtrees') - .insert([ data ]) - .select('*') - .single(); - if (error) throw error; - return inserted; +const sharedTrees ={ + addSharedTree: async(data) => { + return db('sharedTrees').insert(data, ['id', 'token']) }, getALLSharedTree: async () => { - const { data, error } = await supabase - .from('sharedtrees') - .select('*'); - if (error) throw error; - return data; + return db('sharedTrees').select('*'); }, + - getSharedTreeById: async (id) => { - const { data, error } = await supabase - .from('sharedtrees') - .select('*') - .eq('sharedtreeid', id) - .single(); - if (error) throw error; - return data; + getSharedTreeById: async(id) =>{ + return db('sharedTrees').where('sharedTreeID',id).first(); }, - getSharedTreebySender: async (id) => { - const { data, error } = await supabase - .from('sharedtrees') - .select('*') - .eq('senderid', id); - if (error) throw error; - return data; + getSharedTreebySender: async(id) => { + return db('sharedTrees').where('senderId', id); }, - getSharedTreebyReciever: async (id) => { - const { data, error } = await supabase - .from('sharedtrees') - .select('*') - .eq('recieverid', id); - if (error) throw error; - return data; + getSharedTreebyReciever: async(id) => { + return db('sharedTrees').where({ recieverId: id }).select('*'); }, getSharedTreeByToken: async (token) => { - const { data, error } = await supabase - .from('sharedtrees') - .select('*') - .eq('token', token) - .maybeSingle(); - if (error) throw error; - return data; + return db('sharedTrees').where('token', token).first(); + }, + + shareTree: async(data) => { + return db('relationship').where('person1_id', personId).andWhere('side','side'); + }, + + mergeTree: async(id, data) => { + for (const member of data){ + await db('treeMembers').insert({ + owner_id: recieverID, + name : member.name, + relationship: member.relationship, + }); + } + return {message: "Members merged successfully"}; + + }, + + getMemberstoMerge: async(senderId, recieverId) => { + return db('sharedTrees').where(senderId, senderId).select('*'); } + + }; module.exports = sharedTrees; \ No newline at end of file diff --git a/server/models/treeInfoModel.js b/server/models/treeInfoModel.js index 7ef2122..82627e1 100644 --- a/server/models/treeInfoModel.js +++ b/server/models/treeInfoModel.js @@ -1,36 +1,18 @@ -// treeInfoModel.js - model for treeInfo table (Supabase) -const supabase = require('../lib/supabase'); +const db = require('../db/knex'); const treeInfo = { addObject: async (data) => { - const { data: inserted, error } = await supabase - .from('treeinfo') - .insert([ data ]) - .select('*') - .single(); - if (error) throw error; - return inserted; + return db('treeInfo').insert(data, ['id']); }, - updateObject: async (userId, data) => { - const { data: updated, error } = await supabase - .from('treeinfo') - .update(data) - .eq('userid', userId) - .select('*') - .single(); - if (error) throw error; - return updated; + updateObject: async (id, data) => { + await db('treeInfo').where({ userId: id }).update(data); + const updatedObject = await db('treeInfo').where({ id }).first(); + return updatedObject; }, - getObject: async (userId) => { - const { data, error } = await supabase - .from('treeinfo') - .select('*') - .eq('userid', userId) - .maybeSingle(); - if (error) throw error; - return data; + getObject: async (id) => { + return db('treeInfo').where({ userId: id }).first(); }, }; diff --git a/server/models/treeMemberModel.js b/server/models/treeMemberModel.js index 5badff6..459f6a5 100644 --- a/server/models/treeMemberModel.js +++ b/server/models/treeMemberModel.js @@ -1,148 +1,53 @@ -// treeMemberModel.js - the model for the treeMembers table -// this file was replaced with the supabase model -const supabase = require('../lib/supabase'); +const db = require('../db/knex'); -// all functions for the treeMember to interact with the database const treeMember = { addMember: async (data) => { - // Map camelCase to lowercase column names for Postgres - const mappedData = { - firstname: data.firstName || data.firstname, - lastname: data.lastName || data.lastname, - birthdate: data.birthDate || data.birthdate, - deathdate: data.deathDate || data.deathdate, - location: data.location, - phonenumber: data.phoneNumber || data.phonenumber, - userid: data.userId || data.userid, - memberuserid: data.memberUserId || data.memberuserid, - }; - // Remove undefined/null values - Object.keys(mappedData).forEach(key => mappedData[key] === undefined && delete mappedData[key]); - - // Validate that userid is an integer (not a UUID) - if (mappedData.userid && (typeof mappedData.userid === 'string' && mappedData.userid.includes('-'))) { - throw new Error(`Invalid userid: expected integer, got UUID: ${mappedData.userid}`); - } - if (mappedData.memberuserid && (typeof mappedData.memberuserid === 'string' && mappedData.memberuserid.includes('-'))) { - throw new Error(`Invalid memberuserid: expected integer, got UUID: ${mappedData.memberuserid}`); - } - - console.log('addMember mappedData:', JSON.stringify(mappedData, null, 2)); - const { data: inserted, error } = await supabase - .from('treemembers') - .insert([ mappedData ]) - .select('id') - .single(); - if (error) { - console.error('addMember Supabase error:', error); - throw error; - } - return inserted; + return db('treeMembers').insert(data, ['id']); }, getAllMembers: async () => { - const { data, error } = await supabase - .from('treemembers') - .select('*'); - if (error) throw error; - return data; + return db('treeMembers').select('*'); }, + getAllMembersbyId: async (id) => { - const { data, error } = await supabase - .from('treemembers') - .select('*') - .eq('id', id); - if (error) throw error; - return data; + return db('treeMembers').where({id}).select('*'); }, - getMemberById: async (id) => { - const { data, error } = await supabase - .from('treemembers') - .select('*') - .eq('id', id) - .single(); - if (error) throw error; - return data; + getMemberById: async (id) => { // Fixed the typo + return db('treeMembers').where({ id }).first(); }, getMembersByUser: async (userId) => { - const { data, error } = await supabase - .from('treemembers') - .select('*') - .eq('userid', userId); - if (error) throw error; - return data; + return db('treeMembers').where({userId}).select('*'); }, getMembeByOtherUser: async (userId) => { - const { data, error } = await supabase - .from('treemembers') - .select('*') - .not('userid', 'eq', userId); - if (error) throw error; - return data; + return db('treeMembers').whereNot({userId}).select('*'); + }, - // i cant get this to workkkkkkk updateMemberInfo: async (id, data) => { - // Map camelCase to lowercase column names for Postgres - const mappedData = {}; - if (data.firstName !== undefined) mappedData.firstname = data.firstName; - if (data.lastName !== undefined) mappedData.lastname = data.lastName; - if (data.birthDate !== undefined) mappedData.birthdate = data.birthDate; - if (data.deathDate !== undefined) mappedData.deathdate = data.deathDate; - if (data.location !== undefined) mappedData.location = data.location; - if (data.phoneNumber !== undefined) mappedData.phonenumber = data.phoneNumber; - if (data.userId !== undefined) mappedData.userid = data.userId; - if (data.memberUserId !== undefined) mappedData.memberuserid = data.memberUserId; - // Also handle lowercase variants - if (data.firstname !== undefined) mappedData.firstname = data.firstname; - if (data.lastname !== undefined) mappedData.lastname = data.lastname; - if (data.birthdate !== undefined) mappedData.birthdate = data.birthdate; - if (data.deathdate !== undefined) mappedData.deathdate = data.deathdate; - if (data.phonenumber !== undefined) mappedData.phonenumber = data.phonenumber; - if (data.userid !== undefined) mappedData.userid = data.userid; - if (data.memberuserid !== undefined) mappedData.memberuserid = data.memberuserid; - const { data: updated, error } = await supabase - .from('treemembers') - .update(mappedData) - .eq('id', id) - .select('*') - .single(); - if (error) throw error; - return updated; + await db('treeMembers').where({ id }).update(data); + const updatedRecord = await db('treeMembers').where({ id }).first(); + return updatedRecord; }, - assignNewMemberRelationship: async (recieverId, getMemberById, relationshipType) => { - // Update an existing relationship record tying two members together - const { error } = await supabase - .from('relationships') - .update({ relationshipType }) - .match({ person1_id: recieverId, person2_id: getMemberById }); - if (error) throw error; - return { success: true }; + return db('treeMembers').where({person1_id: recieverId, person2_id: recieverId}).update({relationshipType: relationshipType}) }, + deleteByUser: async (userId) => { - const { error } = await supabase - .from('treemembers') - .delete() - .eq('userid', userId); - if (error) throw error; + return db('treeMembers').where({userId}).del(); }, getActiveMemberId: async (id) => { - const { data, error } = await supabase - .from('treemembers') - .select('*') - .eq('userid', id) - .eq('memberuserid', id) - .maybeSingle(); - if (error) throw error; - return data; + // userId and memberUserId are both equal to the id + return db('treeMembers').where({userId: id, memberUserId: id}).first(); + } + + }; module.exports = treeMember; diff --git a/server/models/treeSummaryModel.js b/server/models/treeSummaryModel.js index f82b830..f83847d 100644 --- a/server/models/treeSummaryModel.js +++ b/server/models/treeSummaryModel.js @@ -1,58 +1,16 @@ -// treeSummaryModel.js - model for tree summaries (Supabase) -// Note: This table may need to be created in Supabase if it doesn't exist -const supabase = require('../lib/supabase'); +const db = require('../db/knex'); const treeSummary = { getSummaryByUser: async (userId) => { - // Resolve UUID to integer if needed - let userIdInt = userId; - if (typeof userId === 'string' && userId.includes('-')) { - const User = require('./userModel'); - userIdInt = await User.resolveUserIdFromAuthUid(userId); - if (!userIdInt) return null; - } - const { data, error } = await supabase - .from('usertreesummaries') - .select('*') - .eq('userid', userIdInt) - .maybeSingle(); - if (error) throw error; - return data; + return db('userTreeSummaries').where({userId}).first(); }, - createSummary: async (userId, userData) => { - // Resolve UUID to integer if needed - let userIdInt = userId; - if (typeof userId === 'string' && userId.includes('-')) { - const User = require('./userModel'); - userIdInt = await User.resolveUserIdFromAuthUid(userId); - if (!userIdInt) throw new Error('User not found'); - } - const { data, error } = await supabase - .from('usertreesummaries') - .insert([{ userid: userIdInt, currenttreesummary: userData }]) - .select('*') - .single(); - if (error) throw error; - return data; + createSummary: async (userId, userData) =>{ + return db('userTreeSummaries').insert({'userId': userId, 'currentTreeSummary': userData}); }, - updateSummary: async (userId, userData) => { - // Resolve UUID to integer if needed - let userIdInt = userId; - if (typeof userId === 'string' && userId.includes('-')) { - const User = require('./userModel'); - userIdInt = await User.resolveUserIdFromAuthUid(userId); - if (!userIdInt) throw new Error('User not found'); - } - const { data, error } = await supabase - .from('usertreesummaries') - .update({ currenttreesummary: userData }) - .eq('userid', userIdInt) - .select('*') - .single(); - if (error) throw error; - return data; + updateSummary: async (userId, userData) =>{ + return db('userTreeSummaries').where({userId}).update({'currentTreeSummary': userData}); } }; diff --git a/server/models/userModel.js b/server/models/userModel.js index b7c5f7d..49d9eae 100644 --- a/server/models/userModel.js +++ b/server/models/userModel.js @@ -1,112 +1,32 @@ -// userModel.js - the model for the user table -// this file was replaced with the supabase model -const supabase = require('../lib/supabase'); +const db = require('../db/knex'); +const { get } = require('../routes/treeMemberRoute'); -// all functions for the user to interact with the database const User = { register: async (userData) => { - const { data, error } = await supabase - .from('users') - .insert([ userData ]) - .select('id, firstname, lastname, email, phonenumber, birthdate') - .single(); - if (error) throw error; - return data; + return db('users').insert(userData, ['id', 'firstName', 'lastName', 'email']); }, findByEmail: async (email) => { - const { data, error } = await supabase - .from('users') - .select('*') - .eq('email', email) - .maybeSingle(); - if (error) throw error; - return data; + return db('users').where({email}).first(); }, findById: async (id) => { - const { data, error } = await supabase - .from('users') - .select('*') - .eq('id', id) - .single(); - if (error) throw error; - return data; + return db('users').where({id}).first(); }, - updateUserInfo: async (id, userData) => { - const { data, error } = await supabase - .from('users') - .update(userData) - .eq('id', id) - .select('*') - .single(); - if (error) throw error; - return data; + updateUserInfo: async(id, userData) => { + return db('users').insert(userData, '').where({id}).first().insert(userData, []); }, + deleteUser: async (id) => { - const { error } = await supabase - .from('users') - .delete() - .eq('id', id); - if (error) throw error; + return db('users').where({id}).del(); }, getAllUsers: async () => { - const { data, error } = await supabase - .from('users') - .select('*'); - if (error) throw error; - return data; - }, - - findByAuthUid: async (authUid) => { - const { data, error } = await supabase - .from('users') - .select('*') - .eq('auth_uid', authUid) - .maybeSingle(); - if (error) throw error; - return data; - }, - - upsertByAuthUser: async ({ auth_uid, email, username, firstName, lastName, phoneNumber, birthDate }) => { - // Map to lowercase columns and drop null/undefined so we don't overwrite with nulls - const rawPayload = { - auth_uid, - email, - username, - firstname: firstName, - lastname: lastName, - phonenumber: phoneNumber, - birthdate: birthDate, - }; - const payload = Object.fromEntries( - Object.entries(rawPayload).filter(([_, v]) => v !== undefined && v !== null && v !== '') - ); - const { data, error } = await supabase - .from('users') - .upsert([ payload ], { onConflict: 'auth_uid' }) - .select('id, auth_uid, email, username, firstname, lastname, phonenumber, birthdate') - .single(); - if (error) throw error; - return data; + return db('users').select('*'); }, -}; -// Helper to resolve UUID (auth_uid) to integer user ID -const resolveUserIdFromAuthUid = async (authUidOrIntId) => { - // If it's already an integer, return it - if (!isNaN(authUidOrIntId) && !authUidOrIntId.toString().includes('-')) { - return parseInt(authUidOrIntId); - } - // Otherwise look up by auth_uid - const user = await User.findByAuthUid(authUidOrIntId); - if (!user) return null; - return user.id; }; -User.resolveUserIdFromAuthUid = resolveUserIdFromAuthUid; - module.exports = User; \ No newline at end of file diff --git a/server/mysql-connection.js b/server/mysql-connection.js new file mode 100644 index 0000000..191170d --- /dev/null +++ b/server/mysql-connection.js @@ -0,0 +1,43 @@ +require('dotenv').config(); +const mysql = require('mysql2'); + +console.log('Database Config:', process.env.DB_USER, process.env.DB_PASSWORD, process.env.DB_DATABASE); + +const connection = mysql.createConnection({ + host: process.env.DB_HOST, // localhost + user: process.env.DB_USER, // Make sure DB_USER is set + password: process.env.DB_PASSWORD, // Ensure DB_PASSWORD is set + database: process.env.DB_DATABASE, // Ensure DB_DATABASE is set + port: process.env.DB_PORT || 3306 // Port should be 3306 +}); + +connection.connect((err) => { + if (err) { + console.error('Error connecting to MySQL:', err.stack); + return; + } + + console.log('Connected to MySQL as id ' + connection.threadId); + + // Example query to check connection + + connection.query('SELECT DATABASE()', (err, results) => { + if (err) { + console.error('Error running query:', err.stack); + return; + } + console.log('Connected to the database:', results[0]['DATABASE()']); + }); + + connection.query('SHOW DATABASES', (err, results) => { + if (err) { + console.error('Error fetching databases:', err); + } else { + console.log('Databases:', results.map(db => db.Database)); + } + connection.end(); // Close the connection + }); + + // Close the connection + connection.end(); +}); diff --git a/server/package-lock.json b/server/package-lock.json index 56effa1..99734e4 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,7 +9,6 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@supabase/supabase-js": "^2.74.0", "bcryptjs": "^2.4.3", "cors": "^2.8.5", "dotenv": "^16.5.0", @@ -563,104 +562,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@supabase/auth-js": { - "version": "2.74.0", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.74.0.tgz", - "integrity": "sha512-EJYDxYhBCOS40VJvfQ5zSjo8Ku7JbTICLTcmXt4xHMQZt4IumpRfHg11exXI9uZ6G7fhsQlNgbzDhFN4Ni9NnA==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "2.6.15" - } - }, - "node_modules/@supabase/functions-js": { - "version": "2.74.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.74.0.tgz", - "integrity": "sha512-VqWYa981t7xtIFVf7LRb9meklHckbH/tqwaML5P3LgvlaZHpoSPjMCNLcquuLYiJLxnh2rio7IxLh+VlvRvSWw==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "2.6.15" - } - }, - "node_modules/@supabase/node-fetch": { - "version": "2.6.15", - "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", - "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/@supabase/postgrest-js": { - "version": "2.74.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.74.0.tgz", - "integrity": "sha512-9Ypa2eS0Ib/YQClE+BhDSjx7OKjYEF6LAGjTB8X4HucdboGEwR0LZKctNfw6V0PPIAVjjzZxIlNBXGv0ypIkHw==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "2.6.15" - } - }, - "node_modules/@supabase/realtime-js": { - "version": "2.74.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.74.0.tgz", - "integrity": "sha512-K5VqpA4/7RO1u1nyD5ICFKzWKu58bIDcPxHY0aFA7MyWkFd0pzi/XYXeoSsAifnD9p72gPIpgxVXCQZKJg1ktQ==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "2.6.15", - "@types/phoenix": "^1.6.6", - "@types/ws": "^8.18.1", - "ws": "^8.18.2" - } - }, - "node_modules/@supabase/storage-js": { - "version": "2.74.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.74.0.tgz", - "integrity": "sha512-o0cTQdMqHh4ERDLtjUp1/KGPbQoNwKRxUh6f8+KQyjC5DSmiw/r+jgFe/WHh067aW+WU8nA9Ytw9ag7OhzxEkQ==", - "license": "MIT", - "dependencies": { - "@supabase/node-fetch": "2.6.15" - } - }, - "node_modules/@supabase/supabase-js": { - "version": "2.74.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.74.0.tgz", - "integrity": "sha512-IEMM/V6gKdP+N/X31KDIczVzghDpiPWFGLNjS8Rus71KvV6y6ueLrrE/JGCHDrU+9pq5copF3iCa0YQh+9Lq9Q==", - "license": "MIT", - "dependencies": { - "@supabase/auth-js": "2.74.0", - "@supabase/functions-js": "2.74.0", - "@supabase/node-fetch": "2.6.15", - "@supabase/postgrest-js": "2.74.0", - "@supabase/realtime-js": "2.74.0", - "@supabase/storage-js": "2.74.0" - } - }, - "node_modules/@types/node": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz", - "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.14.0" - } - }, - "node_modules/@types/phoenix": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", - "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", - "license": "MIT" - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -2364,12 +2265,6 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -2389,12 +2284,6 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "license": "MIT" }, - "node_modules/undici-types": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", - "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", - "license": "MIT" - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2454,43 +2343,6 @@ "node": ">= 0.8" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/server/package.json b/server/package.json index cd8e1cb..6a0f438 100644 --- a/server/package.json +++ b/server/package.json @@ -11,7 +11,6 @@ "author": "", "license": "ISC", "dependencies": { - "@supabase/supabase-js": "^2.74.0", "bcryptjs": "^2.4.3", "cors": "^2.8.5", "dotenv": "^16.5.0", diff --git a/server/routes/ContactRoutes.js b/server/routes/ContactRoutes.js new file mode 100644 index 0000000..f65fbb2 --- /dev/null +++ b/server/routes/ContactRoutes.js @@ -0,0 +1,28 @@ +const express = require('express'); +const router = express.Router(); + +const { + addContact, + addMultipleContacts, + getContactsByUser, + getContactById, + updateContact, + deleteContact, + deleteByUser +} = require('../controllers/contactController'); + +router.post('/add', addContact); + +router.post('/add-multiple', addMultipleContacts); + +router.get('/user/:userId', getContactsByUser); + +router.get('/:id', getContactById); + +router.put('/:id', updateContact); + +router.delete('/:id', deleteContact); + +router.delete('/user/:userId', deleteByUser); + +module.exports = router; \ No newline at end of file diff --git a/server/routes/authRoutes.js b/server/routes/authRoutes.js index 4b3636e..3121695 100644 --- a/server/routes/authRoutes.js +++ b/server/routes/authRoutes.js @@ -2,13 +2,16 @@ const express = require('express'); const router = express.Router(); -const { deleteByUser, findByEmail, findById, getAllUsers, syncAuthUser } = require('../controllers/authController'); +const { register, login, deleteByUser, findByEmail, findById, getAllUsers, } = require('../controllers/authController'); // Assuming you have a controller for your registration logic +console.log('Register function:', register); + +router.post('/register', register); +router.post('/login', login); router.delete('/remove/:id', deleteByUser); router.get('/user/:id', findById); router.get('/user/email/:email', findByEmail); router.get('/users', getAllUsers); -router.post('/sync', syncAuthUser); module.exports = router; diff --git a/server/routes/treeMemberRoute.js b/server/routes/treeMemberRoute.js index 7c45829..5435d13 100644 --- a/server/routes/treeMemberRoute.js +++ b/server/routes/treeMemberRoute.js @@ -2,7 +2,7 @@ const express = require('express'); const router = express.Router(); -const { addTreeMember, editTreeMember,getMembersByUser, getMembersByOtherUser, deleteByUser, getMemberById, getActiveMemberId } = require('../controllers/treeMemberController'); +const { addTreeMember, editTreeMember,getMembersByUser, getMembersByOtherUser, deleteByUser, getMemberById, getActiveMemberId} = require('../controllers/treeMemberController'); router.post('/', addTreeMember); router.put('/:id', editTreeMember); diff --git a/server/server.js b/server/server.js index f2539c3..7cf76c9 100644 --- a/server/server.js +++ b/server/server.js @@ -1,19 +1,31 @@ // server.js const express = require('express'); +const knex = require('knex'); const dotenv = require('dotenv'); const cors = require('cors'); +const knexConfig = require('./knexfile'); const authRoutes = require('./routes/authRoutes'); -const treeMemberRoutes = require('./routes/treeMemberRoute'); -const relationshipRoutes = require('./routes/relationshipRoutes'); +const treeMemberRoutes = require('./routes/treeMemberRoute'); // Fixed typo +const relationshipRoutes = require('./routes/relationshipRoutes'); // Fixed typo const sharedTreeRoutes = require('./routes/sharedTreeRoutes'); const backupRoutes = require('./routes/backupRoutes'); -const treeInfoRoutes = require('./routes/treeInfoRoutes'); +const treeInfoRoutes = require('./routes/treeInfoRoutes'); // Fixed typo dotenv.config(); const app = express(); const port = process.env.PORT || 5000; +const db = knex({ + client: 'mysql2', + connection: { + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME + } +}); + app.use(express.json()); app.use(cors()); @@ -27,3 +39,20 @@ app.use('/api/tree-info', treeInfoRoutes); app.listen(port, () => { console.log(`Server running on port ${port}`); }); + + +// Example route -- follow this template for other routes + +/* +app.get('/api/items', async (req, res) => { + try { + const items = await db('items').select('*'); + res.json(items); + } catch (error) { + res.status(500).json({ error: 'An error occurred' }); + } + }); + +*/ + +