diff --git a/inventory-manager/src/App.jsx b/inventory-manager/src/App.jsx index a74771039297dfb6170a9b0bc1f799ad7e83951a..eda8f9717d7e7514e949bea16acd123ed4c633b3 100644 --- a/inventory-manager/src/App.jsx +++ b/inventory-manager/src/App.jsx @@ -15,6 +15,10 @@ import { useState } from "react"; import useToken from "./components/useToken"; import AccountInformation from "./components/user/AccountInformation"; import PrivateRoutes from "./routes/PrivateRoutes"; +import MyOrganizations from "./components/myorg/MyOrganizations"; +import OrganizationDetails from "./components/myorg/OrganizationDetails"; +import NotFound from "./components/error/NotFound"; +import OrganizationRoster from "./components/myorg/roster/OrganizationRoster"; function App() { // const [token, setToken] = useState(); @@ -45,7 +49,19 @@ function App() { <Route element={<PrivateRoutes token={token} />}> <Route path="/createrequest" element={<CreateRequest />} /> </Route> - + <Route element={<PrivateRoutes token={token} />}> + <Route path="/myorganizations" element={<MyOrganizations token={token}/>} /> + </Route> + <Route element={<PrivateRoutes token={token} />}> + <Route path="/organizations/:orgId" element={<OrganizationDetails token={token}/> } /> + </Route> + <Route element={<PrivateRoutes token={token} />}> + <Route path="/organizations/:orgId/members" element={<OrganizationRoster token={token}/> } /> + </Route> + <Route path='*' element={<NotFound />}/> + <Route path='/404' element={<NotFound />}/> + {/*<Route path="/organizations/:orgId" element={<OrganizationDetails token={token}/>}>*/} + {/*</Route>*/} <Route path="/listallorganizations" element={<ListAllOrganizations />} diff --git a/inventory-manager/src/components/Navbar.jsx b/inventory-manager/src/components/Navbar.jsx index aef328d27db9c8a95707a9a147d109a467b2cc7e..1c737c29e03ea535070f4cbcda3f892b969f8edc 100644 --- a/inventory-manager/src/components/Navbar.jsx +++ b/inventory-manager/src/components/Navbar.jsx @@ -34,7 +34,9 @@ export const Navbar = ({ token }) => { <li> <NavLink to="/accountinfo">Account Information</NavLink> </li> - + <li> + <NavLink to="/myorganizations">My Organizations</NavLink> + </li> <li> <NavLink to="/createorganization">Create Organization</NavLink> </li> diff --git a/inventory-manager/src/components/error/NotFound.css b/inventory-manager/src/components/error/NotFound.css new file mode 100644 index 0000000000000000000000000000000000000000..ff1cc59b7a059f74f96be625acd9425eb5cf9771 --- /dev/null +++ b/inventory-manager/src/components/error/NotFound.css @@ -0,0 +1,26 @@ +.not-found-container { + text-align: center; + margin-top: 50px; +} + +h1 { + font-size: 24px; +} + +p { + font-size: 16px; + margin-bottom: 20px; +} + +/* Change Link to a for proper styling */ +a { + display: block; + margin-bottom: 10px; + font-size: 18px; + text-decoration: none; + color: #3498db; /* Change color to your preference */ +} + +a:hover { + text-decoration: underline; +} diff --git a/inventory-manager/src/components/error/NotFound.jsx b/inventory-manager/src/components/error/NotFound.jsx new file mode 100644 index 0000000000000000000000000000000000000000..b21f5dac7d7b796f347fe7723cedbc7a076fe2f4 --- /dev/null +++ b/inventory-manager/src/components/error/NotFound.jsx @@ -0,0 +1,13 @@ +import { Link } from "react-router-dom"; +import './NotFound.css' +export default function NotFound() { + return ( + <div className="not-found-container"> + <h1>Oops! You seem to be lost.</h1> + <p>Here are some helpful links:</p> + <Link to='/'>Home</Link> + <Link to='/login'>Login</Link> + <Link to='/register'>Register</Link> + </div> + ) +} \ No newline at end of file diff --git a/inventory-manager/src/components/myorg/MyOrganization.css b/inventory-manager/src/components/myorg/MyOrganization.css new file mode 100644 index 0000000000000000000000000000000000000000..ab5206049c8045c0456b465a25eee0cc998d7b5c --- /dev/null +++ b/inventory-manager/src/components/myorg/MyOrganization.css @@ -0,0 +1,17 @@ +.organization-list { + max-width: 600px; + margin: 0 auto; +} + +.organization-item { + border: 1px solid #ccc; + padding: 10px; + margin-bottom: 10px; + border-radius: 5px; +} + +.organization-item h3 { + margin-bottom: 5px; +} + +/* Add more styling as needed */ diff --git a/inventory-manager/src/components/myorg/MyOrganizations.jsx b/inventory-manager/src/components/myorg/MyOrganizations.jsx new file mode 100644 index 0000000000000000000000000000000000000000..07a4b19e0c345da0bd8932cc99d5511382e95c06 --- /dev/null +++ b/inventory-manager/src/components/myorg/MyOrganizations.jsx @@ -0,0 +1,53 @@ +import React, { useState, useEffect } from 'react'; +import Axios from 'axios'; +import './MyOrganization.css' +import PropTypes from "prop-types"; +import {Link} from "react-router-dom"; +const MyOrganizations = ({ token }) => { + const [organizations, setOrganizations] = useState([]); + + useEffect(() => { + const fetchOrganizations = async () => { + try { + console.log(token) + const response = await Axios.post('http://localhost:8080/myorg/user', { + jwt: token.jwt + }); + + if (response.data.result === 'success') { + setOrganizations(response.data.data); + } else { + console.error('Error fetching organizations'); + } + } catch (error) { + console.error('Error fetching organizations:', error); + } + }; + + fetchOrganizations(); + }, [token]); + + return ( + <div className="organization-list"> + <h2>Your Organizations</h2> + <ul> + {organizations.map((org) => ( + <li key={org.orgId} className="organization-item"> + <Link to={`/organizations/${org.orgId}`}> + <h3>{org.name}</h3> + </Link> + <p>Category: {org.category}</p> + <p>Members: {org.memberCount}</p> + {/* Add more information as needed */} + </li> + ))} + </ul> + </div> + ); +}; +MyOrganizations.propTypes = { + token: PropTypes.shape({ + jwt: PropTypes.string.isRequired, + }).isRequired, +}; +export default MyOrganizations; diff --git a/inventory-manager/src/components/myorg/OrganizationDetails.css b/inventory-manager/src/components/myorg/OrganizationDetails.css new file mode 100644 index 0000000000000000000000000000000000000000..c659914a0b82a4042b84820e35595935d4fae221 --- /dev/null +++ b/inventory-manager/src/components/myorg/OrganizationDetails.css @@ -0,0 +1,41 @@ +.organization-details { + text-align: center; + margin-top: 50px; +} + +h2 { + font-size: 24px; + margin-bottom: 20px; +} + +p { + font-size: 16px; + margin-bottom: 10px; +} + +/* Center organization information */ +.organization-info { + max-width: 600px; + margin: 0 auto; +} + +.button-container { + display: flex; + justify-content: center; + margin-top: 20px; +} + +.blue-button { + background-color: #3498db; + color: #fff; + border: 1px solid #2980b9; + padding: 8px 16px; /* Adjusted padding for smaller buttons */ + margin: 0 10px; /* Adjusted margin */ + font-size: 14px; /* Adjusted font size */ + cursor: pointer; + max-width: 10%; +} + +.blue-button:hover { + background-color: #2980b9; +} diff --git a/inventory-manager/src/components/myorg/OrganizationDetails.jsx b/inventory-manager/src/components/myorg/OrganizationDetails.jsx new file mode 100644 index 0000000000000000000000000000000000000000..6d681cc9d129166c79411d4949c6b8e9feec5132 --- /dev/null +++ b/inventory-manager/src/components/myorg/OrganizationDetails.jsx @@ -0,0 +1,61 @@ +import React, { useState, useEffect } from 'react'; +import {useParams, Navigate, useNavigate } from 'react-router-dom'; +import Axios from 'axios'; +import './OrganizationDetails.css' +const OrganizationDetails = ({token}) => { + const { orgId } = useParams(); + const [organization, setOrganization] = useState(null); + const navigate = useNavigate(); + useEffect(() => { + const fetchOrganizationDetails = async () => { + try { + console.log(token); + const response = await Axios.post(`http://localhost:8080/myorg/user/org`, { + jwt: token.jwt, + orgId: orgId + }); + + if (response.data.result === 'success') { + setOrganization(response.data.data); + } else { + console.error('Error fetching organization details'); + navigate('/404'); + } + } catch (error) { + console.error('Error fetching organization details:', error); + navigate('/404'); + } + }; + + fetchOrganizationDetails(); + }, [orgId]); + + const handleRosterButtonClick = () => { + // Redirect to the OrganizationMembers page + navigate(`/organizations/${orgId}/members`); + }; + if (!organization) { + return <div>Loading...</div>; + } + + return ( + <div className="organization-details"> + <div className="organization-info"> + <h2>{organization.name}</h2> + <p>Email: {organization.email}</p> + <p>Owner Email: {organization.ownerEmail}</p> + <p>Description: {organization.description}</p> + <p>Member Count: {organization.memberCount}</p> + </div> + {/* Buttons at the bottom */} + <div className="button-container"> + <button className="blue-button" onClick={handleRosterButtonClick}>Roster</button> + <button className="blue-button">Requests</button> + <button className="blue-button">Items</button> + <button className="blue-button">Listings</button> + </div> + </div> + ); +}; + +export default OrganizationDetails; diff --git a/inventory-manager/src/components/myorg/roster/OrganizationRoster.css b/inventory-manager/src/components/myorg/roster/OrganizationRoster.css new file mode 100644 index 0000000000000000000000000000000000000000..b23b785560939135235ef340c32bc05adcefcea3 --- /dev/null +++ b/inventory-manager/src/components/myorg/roster/OrganizationRoster.css @@ -0,0 +1,48 @@ +.organization-members { + text-align: center; + margin-top: 50px; + padding: 20px; /* Add padding for more space */ +} + +h2 { + font-size: 24px; +} + +.members-list { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 20px; +} + +.member-item { + display: flex; + flex-direction: column; /* Display columns instead of rows */ + width: 300px; + margin-bottom: 20px; /* Increase margin for better spacing */ + padding: 16px; + border: 1px solid #ddd; + border-radius: 4px; +} + +.member-item span { + margin-bottom: 10px; /* Add space between each span */ +} + +.member-type { + padding: 8px 16px; + border-radius: 4px; + color: #fff; +} + +.owner { + background-color: #3498db; +} + +.member { + background-color: #2ecc71; +} + +.manager { + background-color: #e74c3c; +} diff --git a/inventory-manager/src/components/myorg/roster/OrganizationRoster.jsx b/inventory-manager/src/components/myorg/roster/OrganizationRoster.jsx new file mode 100644 index 0000000000000000000000000000000000000000..ea3e76a1be670736aaebf2453add5468c0e188cd --- /dev/null +++ b/inventory-manager/src/components/myorg/roster/OrganizationRoster.jsx @@ -0,0 +1,116 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import Axios from 'axios'; + +import './OrganizationRoster.css'; // Import the CSS file for styling + +const OrganizationMembers = ({ token }) => { + const { orgId } = useParams(); + const [roster, setRoster] = useState([]); + const [type, setType] = useState(''); + const [email, setEmail] = useState(''); + const [loading, setLoading] = useState(true); + const navigate = useNavigate(); + useEffect(() => { + const fetchOrganizationMembers = async () => { + try { + const response = await Axios.post('http://localhost:8080/myorg/user/roster', { + orgId: orgId, + jwt: token.jwt, + }); + + if (response.data.result === 'success') { + setRoster(response.data.roster); + setType(response.data.type); + console.log(response.data.userEmail); + setEmail(response.data.userEmail); + } else { + console.error('Error fetching organization members'); + navigate('/404'); + } + } catch (error) { + console.error('Error fetching organization members:', error); + navigate('/404'); + } finally { + setLoading(false); + } + }; + + fetchOrganizationMembers(); + }, [orgId, token, navigate]); + + const canModifyUser = (userType, userEmail) => { + // Logic to determine if the logged-in user can modify the user with userType + // You need to implement this based on your specific rules + // Example: Members can't modify, Managers can modify members, Owners can modify managers and delete members + if (userEmail === email) + return false; //can't edit yourself + // return true; + return userType === 'MEMBER' && type === 'MANAGER' || type === 'OWNER'; + }; + + const handleDropDownChange = async (userEmail, selectedOption) => { + if (window.confirm(`Are you sure you want to change the user type to ${selectedOption}?`)) { + // Logic to handle the drop-down change + // You can implement this based on your requirements + console.log(`Changing type for ${userEmail} to ${selectedOption}`); + try { + const response = await Axios.put('http://localhost:8080/myorg/user/update', { + orgId: orgId, + jwt: token.jwt, + newtype: selectedOption, + memberEmail: userEmail + }); + if (response.data.result === 'success') + { + window.location.reload(false); //refresh the page, remount component and render based on new perms + } + else + { + console.log(response); + } + } catch (error) { + console.error('Error fetching organization members:', error); + navigate('/404'); + } + } else { + // Reset the drop-down to the default value if the user cancels the confirmation + document.getElementById(`${userEmail}-dropdown`).value = 'MODIFY_USER'; + } + }; + + if (loading) { + return <div>Loading...</div>; + } + return ( + <div className="organization-members"> + <h2>Organization Members</h2> + <div className="members-list"> + {roster.map((member, index) => ( + <div key={index} className="member-item"> + <span>{member[1]}, {member[0]}</span> + <span>Email: {member[2]}</span> + <span className={`member-type ${member[3].toLowerCase()}`}>{member[3]}</span> + {canModifyUser(member[3], member[2]) && ( + <select + id={`${member[2]}-dropdown`} + defaultValue="MODIFY_USER" + onChange={(e) => handleDropDownChange(member[2], e.target.value)}> + <option value="MODIFY_USER" disable hidden> + Modify User + </option> + <option value="DELETE">Delete User</option> + {member[3] === 'MANAGER' && <option value="MEMBER">Make Member</option>} + {type === 'OWNER' && member[3] != 'MANAGER' && <option value="MANAGER">Make Manager</option>} + {type === 'OWNER' && member[3] == 'MANAGER' && <option value="OWNER">Transfer Ownership</option>} + </select> + )} + </div> + ))} + </div> + <p>Your role in the organization: {type}</p> + </div> + ); +}; + +export default OrganizationMembers; diff --git a/phase 1.sql b/phase 1.sql index ff937c6a394f6272c9867e6ed86b17953e128bcd..1ec2eef050e8f35d5fece68fb0387ae34dc37ec9 100644 --- a/phase 1.sql +++ b/phase 1.sql @@ -18,14 +18,14 @@ INSERT INTO USER (email, lname, fname, password, phone_number) VALUES SELECT * FROM USER; -- DELETE FROM USER; - +DROP TABLE ORGANIZATION; CREATE TABLE IF NOT EXISTS ORGANIZATION ( organization_id INT AUTO_INCREMENT, name VARCHAR(256) NOT NULL, email VARCHAR(128) NOT NULL, description VARCHAR(1024), owner_email VARCHAR(128) NOT NULL, - category ENUM('ACADEMIC', 'RECREATION', 'TECHNOLOGY', 'POLITICS', 'GREEK LIFE'), + category ENUM('ACADEMIC', 'RECREATION', 'TECHNOLOGY', 'POLITICS', 'GREEKLIFE'), member_count INT DEFAULT 1, PRIMARY KEY (organization_id), CONSTRAINT fk_user_organization FOREIGN KEY (owner_email) REFERENCES USER (email) @@ -36,17 +36,52 @@ INSERT INTO ORGANIZATION (name, email, description, owner_email, category, membe ('Chess Society', 'chesssociety@example.com', 'Organization for chess lovers', 'alicedoe@example.com', 'RECREATION', 20), ('Science Association', 'science@example.com', 'Encouraging scientific exploration', 'johnsmith@example.com', 'ACADEMIC', 30), ('Political Discussion Group', 'politics@example.com', 'Discussions on current political affairs', 'alicedoe@example.com', 'POLITICS', 25), -('Greek Life Association', 'greeklife@example.com', 'Promoting Greek culture and traditions', 'emilyjohnson@example.com', 'GREEK LIFE', 40); +('Greek Life Association', 'greeklife@example.com', 'Promoting Greek culture and traditions', 'emilyjohnson@example.com', 'GREEKLIFE', 40); +DROP TABLE ORGANIZATION_ROSTER; CREATE TABLE IF NOT EXISTS ORGANIZATION_ROSTER ( + roster_id INT AUTO_INCREMENT NOT NULL, user_email VARCHAR(128) NOT NULL, organization_id INT NOT NULL, - type ENUM('MEMBER', 'MANAGER') NOT NULL, - PRIMARY KEY (user_email, organization_id), + type ENUM('MEMBER', 'MANAGER', 'OWNER') NOT NULL, + PRIMARY KEY (roster_id), CONSTRAINT fk_user_manager FOREIGN KEY (user_email) REFERENCES USER (email), CONSTRAINT fk_organization_manager FOREIGN KEY (organization_id) REFERENCES ORGANIZATION (organization_id) ); +INSERT INTO ORGANIZATION_ROSTER (user_email, organization_id, type) +SELECT owner_email, organization_id, 'OWNER' +FROM ORGANIZATION; + +INSERT INTO ORGANIZATION_ROSTER (user_email, organization_id, type) +VALUES + ('johnsmith@example.com', 2, 'MEMBER'), + ('johnsmith@example.com', 4, 'MANAGER'), + ('alicedoe@example.com', 5, 'MEMBER'), + ('emilyjohnson@example.com', 2, 'MEMBER'); + +INSERT INTO ORGANIZATION_ROSTER(user_email, organization_id, type) +VALUES + ('johnsmith@example.com', 2, 'MEMBER'), + ('emilyjohnson@example.com', 2, 'MANAGER'); + +UPDATE ORGANIZATION o +SET member_count = ( + SELECT COUNT(DISTINCT user_email) + FROM ORGANIZATION_ROSTER + WHERE organization_id = o.organization_id +) +WHERE member_count != o.member_count; + + + +SELECT * FROM ORGANIZATION_ROSTER WHERE ORGANIZATION_ROSTER.user_email LIKE 'emilyjohnson@example.com'; +SELECT DISTINCT o.* +FROM ORGANIZATION o + JOIN ORGANIZATION_ROSTER r ON o.organization_id = r.organization_id +WHERE r.user_email = 'emilyjohnson@example.com' + OR o.owner_email = 'emilyjohnson@example.com'; + CREATE TABLE IF NOT EXISTS REQUEST ( request_id INT AUTO_INCREMENT NOT NULL, user_email VARCHAR(128) NOT NULL, diff --git a/src/main/java/com/example/accessingdatamysql/MainController.java b/src/main/java/com/example/accessingdatamysql/MainController.java index 707d039890a862304e39809de97cf70915a56c47..ec49b10cec5e57af32d29a100594c9a0400236a5 100644 --- a/src/main/java/com/example/accessingdatamysql/MainController.java +++ b/src/main/java/com/example/accessingdatamysql/MainController.java @@ -64,7 +64,7 @@ public class MainController { } @PostMapping(path = "/user") - public @ResponseBody User getUser(@RequestBody Map<String, String> json) + public @ResponseBody User getUser(@RequestBody Map<String, Object> json) { User found = new User(); AuthController au = new AuthController(); @@ -85,7 +85,7 @@ public class MainController { @PostMapping(path = "/delete") @ResponseBody - public User deleteUser(@RequestBody Map<String, String> json) + public User deleteUser(@RequestBody Map<String, Object> json) { User found = new User(); AuthController au = new AuthController(); diff --git a/src/main/java/com/example/accessingdatamysql/auth/AuthController.java b/src/main/java/com/example/accessingdatamysql/auth/AuthController.java index 7afd8e6d263c661510baadeda11a34a19f96a053..626c304e1ed909d4c0dba83cf9206518b44c6519 100644 --- a/src/main/java/com/example/accessingdatamysql/auth/AuthController.java +++ b/src/main/java/com/example/accessingdatamysql/auth/AuthController.java @@ -61,12 +61,13 @@ public class AuthController { //also create a verification end point to verify their access to this one org. @PostMapping(path="/verify") - public @ResponseBody Map<String, String> verify(@RequestBody Map<String, String> json) + public @ResponseBody Map<String, String> verify(@RequestBody Map<String, Object> json) { Map<String, String> res = new HashMap<String, String>(); + System.out.println(json.entrySet()); if (json.containsKey("jwt")) { - Claims claim = JWT.decodeJWT(json.get("jwt")); + Claims claim = JWT.decodeJWT((String) json.get("jwt")); //this will be a string if (claim != null) { res.put("user", claim.getSubject()); diff --git a/src/main/java/com/example/accessingdatamysql/myorg/MyOrgRosterRepository.java b/src/main/java/com/example/accessingdatamysql/myorg/MyOrgRosterRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..4e2921f0fe2fa4eb791811a24bc5ae332fb75e17 --- /dev/null +++ b/src/main/java/com/example/accessingdatamysql/myorg/MyOrgRosterRepository.java @@ -0,0 +1,203 @@ +package com.example.accessingdatamysql.myorg; + +import com.example.accessingdatamysql.org.Organization; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Query; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.transaction.Transactional; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Repository +public class MyOrgRosterRepository implements OrgRosterRepository{ + + @Autowired + private EntityManager entityManager; + + @Transactional + public List<Organization> findUserOrgs(String userEmail) + { + String nativeQuery = "SELECT DISTINCT o.* " + + "FROM ORGANIZATION o " + + "JOIN ORGANIZATION_ROSTER r ON o.organization_id = r.organization_id " + + "WHERE r.user_email = :userEmail OR o.owner_email = :userEmail"; + List<Organization> organizations = entityManager + .createNativeQuery(nativeQuery, Organization.class) + .setParameter("userEmail", userEmail) + .getResultList(); + return organizations; + } + + @Transactional + public Map<String, Object> findUserOrg(String userEmail, Integer orgId ) + { + Map<String, Object> result = new HashMap<>(); + try { + + String nativeQuery = "SELECT r.type, r.user_email, o.* " + + "FROM ORGANIZATION_ROSTER r " + + "JOIN ORGANIZATION o ON r.organization_id = o.organization_id " + + "WHERE r.user_email = :userEmail AND r.organization_id = :orgId"; + OrgUserType queryResult = (OrgUserType) entityManager.createNativeQuery(nativeQuery, OrgUserType.class) + .setParameter("userEmail", userEmail) + .setParameter("orgId", orgId) + .getSingleResult(); + + if (queryResult != null) { + result.put("result", "success"); + result.put("type", queryResult.getType()); + result.put("userEmail", queryResult.getUserEmail()); + result.put("orgId", orgId); + result.put("data", queryResult); + return result; + } + result.put("result", "failure"); + } + catch (Exception e) + { + result.put("result", "failure"); + //result.put("exception", e.getStackTrace()); + } + return result; + } + + @Transactional + public List<Object> getRoster(Integer orgId) + { + String nativeQuery = "SELECT u.fname, u.lname, u.email, r.type" + + " FROM ORGANIZATION_ROSTER r" + + " JOIN USER u ON r.user_email=u.email" + + " WHERE r.organization_id = :orgId"; + return(entityManager.createNativeQuery(nativeQuery) + .setParameter("orgId", orgId) + .getResultList()); + + } + + @Transactional + public Map<String, Object> updateMember(Integer orgId, String memberEmail, OrganizationRoster.Type type) + { + Map<String, Object> result = new HashMap<>(); + try { + String query = "UPDATE organization_roster SET type = :type " + + "WHERE organization_id = :orgId AND user_email = :memberEmail"; + Query nativeQuery = entityManager.createNativeQuery(query); + nativeQuery.setParameter("type", type.name()); + nativeQuery.setParameter("orgId", orgId); + nativeQuery.setParameter("memberEmail", memberEmail); + + int updatedRows = nativeQuery.executeUpdate(); + + result.put("result", "success"); + result.put("updatedRows", updatedRows); + } catch (Exception e) { + result.put("result", "failure"); + result.put("error", e.getMessage()); + } + return result; + } + + @Transactional + public Map<String, Object> deleteMember(Integer orgId, String memberEmail) + { + Map<String, Object> result = new HashMap<>(); + try { + // Delete the member from ORGANIZATION_ROSTER + String deleteQuery = "DELETE FROM organization_roster WHERE organization_id = :orgId AND user_email = :memberEmail"; + Query deleteNativeQuery = entityManager.createNativeQuery(deleteQuery); + deleteNativeQuery.setParameter("orgId", orgId); + deleteNativeQuery.setParameter("memberEmail", memberEmail); + + int deletedRows = deleteNativeQuery.executeUpdate(); + + if (deletedRows > 0) { + // Decrement the member count in ORGANIZATION + String updateCountQuery = "UPDATE organization SET member_count = member_count - 1 WHERE organization_id = :orgId"; + Query updateCountNativeQuery = entityManager.createNativeQuery(updateCountQuery); + updateCountNativeQuery.setParameter("orgId", orgId); + + int updatedCountRows = updateCountNativeQuery.executeUpdate(); + + result.put("result", "success"); + result.put("deletedRows", deletedRows); + result.put("updatedCountRows", updatedCountRows); + } else { + result.put("result", "failure"); + result.put("error", "Member not found in the organization roster"); + } + } catch (Exception e) { + result.put("result", "failure"); + result.put("error", e.getMessage()); + } + return result; + } + + @Override + public <S extends OrganizationRoster> S save(S entity) { + return null; + } + + @Override + public <S extends OrganizationRoster> Iterable<S> saveAll(Iterable<S> entities) { + return null; + } + + @Override + public Optional<OrganizationRoster> findById(Integer integer) { + return Optional.empty(); + } + + @Override + public boolean existsById(Integer integer) { + return false; + } + + @Override + @Transactional + public List<OrganizationRoster> findAll() { + TypedQuery<OrganizationRoster> query = entityManager.createQuery("SELECT o FROM Organization o", OrganizationRoster.class); + return query.getResultList(); + } + + @Override + public Iterable<OrganizationRoster> findAllById(Iterable<Integer> integers) { + return null; + } + + @Override + public long count() { + return 0; + } + + @Override + public void deleteById(Integer integer) { + + } + + @Override + public void delete(OrganizationRoster entity) { + + } + + @Override + public void deleteAllById(Iterable<? extends Integer> integers) { + + } + + @Override + public void deleteAll(Iterable<? extends OrganizationRoster> entities) { + + } + + @Override + public void deleteAll() { + + } +} diff --git a/src/main/java/com/example/accessingdatamysql/myorg/OrgAuth.java b/src/main/java/com/example/accessingdatamysql/myorg/OrgAuth.java new file mode 100644 index 0000000000000000000000000000000000000000..78691310d76e9c5145187eab828733d3ed480e19 --- /dev/null +++ b/src/main/java/com/example/accessingdatamysql/myorg/OrgAuth.java @@ -0,0 +1,18 @@ +package com.example.accessingdatamysql.myorg; + +import jakarta.persistence.Entity; +import jakarta.persistence.NamedQuery; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +@CrossOrigin +@RestController // This means that this class is a Controller +@RequestMapping(path="/orgauth") // This means URL's start with /orgauth (after Application path) +public class OrgAuth { +// @NamedQuery( +// name="findAllCustomersWithName", +// query="SELECT o FROM Organization o WHERE o.name LIKE :custName" +// ) +} diff --git a/src/main/java/com/example/accessingdatamysql/myorg/OrgRosterController.java b/src/main/java/com/example/accessingdatamysql/myorg/OrgRosterController.java new file mode 100644 index 0000000000000000000000000000000000000000..006a5e6b2d18c008299e8e7ae0e657a50cbf589c --- /dev/null +++ b/src/main/java/com/example/accessingdatamysql/myorg/OrgRosterController.java @@ -0,0 +1,192 @@ +package com.example.accessingdatamysql.myorg; + +import com.example.accessingdatamysql.User; +import com.example.accessingdatamysql.UserRepository; +import com.example.accessingdatamysql.auth.AuthController; +import com.example.accessingdatamysql.auth.JWT; +import com.example.accessingdatamysql.org.Organization; +import jakarta.persistence.criteria.CriteriaBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@CrossOrigin +@RestController // This means that this class is a Controller +@RequestMapping(path="/myorg") // This means URL's start with /orgauth (after Application path) +public class OrgRosterController { + + @Autowired // This means to get the bean called userRepository + // Which is auto-generated by Spring, we will use it to handle the data + private OrgRosterRepository orgRosterRepository; + + @Autowired + private MyOrgRosterRepository myOrgRosterRepository; + + @Autowired + private UserRepository userRepository; + @GetMapping(path="/all") + public @ResponseBody Iterable<OrganizationRoster> getOrgs() + { + return myOrgRosterRepository.findAll(); + } + + @PostMapping(path="/user") + public @ResponseBody Map<String, Object> getUsersOrgs(@RequestBody Map<String, Object> json) + { + Map<String, Object> response = new HashMap<>(); + User found = new User(); + AuthController au = new AuthController(); + Map<String, String> res = au.verify(json); // if the jwt token could not be verified + if (res.containsKey("login") && res.get("login").equals("failed")) + { + response.put("result", "failed = bad token or bad request"); + return response; + } + Optional<User> usr = userRepository.findById(res.get("user")); + if (!usr.isPresent()) + { + response.put("result", "failed = user not found"); + return response; + } + response.put("result", "success"); + response.put("data", myOrgRosterRepository.findUserOrgs(res.get("user"))); + return response; + } + + + @PostMapping(path="/user/org") + public @ResponseBody Map<String, Object> getUserOrg(@RequestBody Map<String, Object> json) + { + Map<String, Object> response = new HashMap<>(); + System.out.println(json.get("orgId")); + if (!json.containsKey("orgId")) + { + response.put("result", "failed = no orgId provided bad request"); + return response; + } + User found = new User(); + AuthController au = new AuthController(); + Map<String, String> res = au.verify(json); // if the jwt token could not be verified + if (res.containsKey("login") && res.get("login").equals("failed")) + { + response.put("result", "failed = bad token or bad request"); + return response; + } + Optional<User> usr = userRepository.findById(res.get("user")); + if (!usr.isPresent()) + { + response.put("result", "failed = user not found"); + return response; + } + if (json.get("orgId") instanceof Integer) + return myOrgRosterRepository.findUserOrg(usr.get().getEmail(), (Integer) json.get("orgId")); + return myOrgRosterRepository.findUserOrg(usr.get().getEmail(), Integer.parseInt((String) json.get("orgId"))); + } + + @PostMapping(path="/user/roster") + public @ResponseBody Map<String, Object> getRoster(@RequestBody Map<String, Object> json) + { + Map<String, Object> map = getUserOrg(json); + Map<String, Object> result = new HashMap<>(); + //above, verify that this user is even supposed to see this information + if (map.get("result").equals("success")) + { + result.put("result", "success"); + result.put("type", map.get("type")); //tell the client what type this user is so they can render buttons for the roster + result.put("userEmail", map.get("userEmail")); + System.out.println(map.get("orgId")); + if (json.get("orgId") instanceof Integer) + result.put("roster", myOrgRosterRepository.getRoster((Integer) json.get("orgId"))); + else + result.put("roster", myOrgRosterRepository.getRoster(Integer.parseInt((String) json.get("orgId")))); + return result; + } + result.put("result", "failure"); + return result; + } + + @PutMapping(path="/user/update") + public @ResponseBody Map<String, Object> updateUser(@RequestBody Map<String, Object> json) + { + Map<String, Object> result = new HashMap<>(); + System.out.println(json.entrySet()); + if (!json.containsKey("orgId") || !json.containsKey("newtype") || !json.containsKey("jwt") || !json.containsKey("memberEmail")) + { + System.out.println("thought there were wrong headers"); + result.put("result", "failure bad request"); + return result; + } + Map<String, Object> map = getUserOrg(json); + //above, verify that this user is even supposed to see this information + if (map.get("result").equals("success")) + { + Integer orgId; + if (json.get("orgId") instanceof Integer) + { + orgId = (Integer) json.get("orgId"); + } + else + { + orgId = Integer.parseInt((String)json.get("orgId")); + } + System.out.println(map.get("type")); + System.out.println(map.get("type").getClass()); + if (map.get("type") == OrganizationRoster.Type.OWNER) + { + if (json.get("newtype").equals("MANAGER") || json.get("newtype").equals("MEMBER")) + { + //simple promotion/demotion case + result.put("result", "success"); + result.put("data", myOrgRosterRepository.updateMember(orgId, (String) json.get("memberEmail"), OrganizationRoster.Type.valueOf((String)json.get("newtype")))); + return result; + } + else if (json.get("newtype").equals("DELETE")) + { + result.put("result", "success"); + result.put("data", myOrgRosterRepository.deleteMember(orgId, (String) json.get("memberEmail"))); + return result; + } + else if (json.get("newtype").equals("OWNER")) + { + result.put("result", "success"); + //transferring ownership + result.put("data", myOrgRosterRepository.updateMember(orgId, (String) json.get("memberEmail"), OrganizationRoster.Type.valueOf((String)json.get("newtype")))); + myOrgRosterRepository.updateMember(orgId, (String) map.get("userEmail"), OrganizationRoster.Type.MANAGER); + return result; + } + else + { + result.put("result", "failure - bad type"); + return result; + } + } + else if (map.get("type") == OrganizationRoster.Type.MANAGER) + { + if (json.get("newtype").equals("DELETE")) + { + //delete this user, update the membercount for the org + result.put("data", myOrgRosterRepository.deleteMember(orgId, (String) json.get("memberEmail"))); + return result; + } + else + { + result.put("result", "failure - permission denied or bad type"); + return result; + } + } + else + { + //members cannot promote or change anyone's type + result.put("result", "failure - permission denied"); + return result; + } + } + result.put("result", "failure"); + return result; + } +} + diff --git a/src/main/java/com/example/accessingdatamysql/myorg/OrgRosterRepository.java b/src/main/java/com/example/accessingdatamysql/myorg/OrgRosterRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..62262d59f06bf3c9e67a3e15819a9f69d53f5285 --- /dev/null +++ b/src/main/java/com/example/accessingdatamysql/myorg/OrgRosterRepository.java @@ -0,0 +1,9 @@ +package com.example.accessingdatamysql.myorg; + +import org.springframework.data.repository.CrudRepository; + +// This will be AUTO IMPLEMENTED by Spring into a Bean called userRepository +// CRUD refers Create, Read, Update, Delete + +public interface OrgRosterRepository extends CrudRepository<OrganizationRoster, Integer>{ +} diff --git a/src/main/java/com/example/accessingdatamysql/myorg/OrgUserType.java b/src/main/java/com/example/accessingdatamysql/myorg/OrgUserType.java new file mode 100644 index 0000000000000000000000000000000000000000..9c0f9e06d0fd5d5bfa317d72914b41bfb89bd13c --- /dev/null +++ b/src/main/java/com/example/accessingdatamysql/myorg/OrgUserType.java @@ -0,0 +1,93 @@ +package com.example.accessingdatamysql.myorg; + +import com.example.accessingdatamysql.org.Organization; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; + +@Entity +public class OrgUserType { + + @Id + private String ownerEmail; + + @Enumerated(EnumType.STRING) + private Organization.Category category; + + private Integer memberCount; + + private String name; + + private String email; + + public String getUserEmail() { + return userEmail; + } + + public void setUserEmail(String userEmail) { + this.userEmail = userEmail; + } + + private String description; + + private String userEmail; + @Enumerated(EnumType.STRING) + private OrganizationRoster.Type type; + + public String getOwnerEmail() { + return ownerEmail; + } + + public void setOwnerEmail(String ownerEmail) { + this.ownerEmail = ownerEmail; + } + + public Organization.Category getCategory() { + return category; + } + + public void setCategory(Organization.Category category) { + this.category = category; + } + + public Integer getMemberCount() { + return memberCount; + } + + public void setMemberCount(Integer memberCount) { + this.memberCount = memberCount; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public OrganizationRoster.Type getType() { + return type; + } + + public void setType(OrganizationRoster.Type type) { + this.type = type; + } +} diff --git a/src/main/java/com/example/accessingdatamysql/myorg/OrganizationRoster.java b/src/main/java/com/example/accessingdatamysql/myorg/OrganizationRoster.java new file mode 100644 index 0000000000000000000000000000000000000000..7d99b7ea74d7b3e949b5407002b106e15ea61419 --- /dev/null +++ b/src/main/java/com/example/accessingdatamysql/myorg/OrganizationRoster.java @@ -0,0 +1,56 @@ +package com.example.accessingdatamysql.myorg; + +import jakarta.persistence.*; + +import java.util.Objects; + +@Entity +@Table(name="OrganizationRoster") +public class OrganizationRoster { + + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + private Integer rosterId; + + private String userEmail; + + private Integer organizationId; + + public Integer getRosterId() { + return rosterId; + } + + public void setRosterId(Integer rosterId) { + this.rosterId = rosterId; + } + + public OrganizationRoster(String userEmail, Integer organizationId, Type type) { + this.userEmail = userEmail; + this.organizationId = organizationId; + this.type = type; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OrganizationRoster that = (OrganizationRoster) o; + return getRosterId().equals(that.getRosterId()) && userEmail.equals(that.userEmail) && organizationId.equals(that.organizationId) && type == that.type; + } + + @Override + public int hashCode() { + return Objects.hash(getRosterId(), userEmail, organizationId, type); + } + + public OrganizationRoster() { + } + + public enum Type { + OWNER, + MANAGER, + MEMBER + } + @Enumerated(EnumType.STRING) + private Type type; +} diff --git a/src/main/java/com/example/accessingdatamysql/OrgController.java b/src/main/java/com/example/accessingdatamysql/org/OrgController.java similarity index 63% rename from src/main/java/com/example/accessingdatamysql/OrgController.java rename to src/main/java/com/example/accessingdatamysql/org/OrgController.java index 3d21cadf73f810a3c21080deb3a5f2eeefea30e0..fc1dff0d49d6af29321c6fdfd3d7a0e8cba081d6 100644 --- a/src/main/java/com/example/accessingdatamysql/OrgController.java +++ b/src/main/java/com/example/accessingdatamysql/org/OrgController.java @@ -1,14 +1,11 @@ -package com.example.accessingdatamysql; +package com.example.accessingdatamysql.org; -import org.apache.coyote.Response; +import com.example.accessingdatamysql.myorg.OrgRosterRepository; +import com.example.accessingdatamysql.myorg.OrganizationRoster; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; -import java.util.Map; -import java.util.Optional; - @CrossOrigin @RestController // This means that this class is a Controller @RequestMapping(path="/organization") // This means URL's start with /demo (after Application path) @@ -17,13 +14,18 @@ public class OrgController { // Which is auto-generated by Spring, we will use it to handle the data private OrgRepository orgRepository; + @Autowired // This means to get the bean called userRepository + // Which is auto-generated by Spring, we will use it to handle the data + private OrgRosterRepository orgRosterRepository; @PostMapping(path = "/add") // Map ONLY POST Requests @ResponseBody public Organization addJsonOrg(@RequestBody Organization org) { // @ResponseBody means the returned String is the response, not a view name // @RequestParam means it is a parameter from the GET or POST request - orgRepository.save(org); + Organization org2 = orgRepository.save(org); + OrganizationRoster orgRoster = new OrganizationRoster(org2.getOwnerEmail(), org2.getOrgId(), OrganizationRoster.Type.OWNER); + orgRosterRepository.save(orgRoster); return org; } diff --git a/src/main/java/com/example/accessingdatamysql/OrgRepository.java b/src/main/java/com/example/accessingdatamysql/org/OrgRepository.java similarity index 70% rename from src/main/java/com/example/accessingdatamysql/OrgRepository.java rename to src/main/java/com/example/accessingdatamysql/org/OrgRepository.java index 63bc2453ace44e3c597b2bd0f2189ec528795253..48c261ff27dee2b8e97524d8a2905d9467449200 100644 --- a/src/main/java/com/example/accessingdatamysql/OrgRepository.java +++ b/src/main/java/com/example/accessingdatamysql/org/OrgRepository.java @@ -1,13 +1,11 @@ -package com.example.accessingdatamysql; +package com.example.accessingdatamysql.org; import org.springframework.data.repository.CrudRepository; -import com.example.accessingdatamysql.Organization; - // This will be AUTO IMPLEMENTED by Spring into a Bean called userRepository // CRUD refers Create, Read, Update, Delete -public interface OrgRepository extends CrudRepository<Organization, String> { +public interface OrgRepository extends CrudRepository<Organization, Integer> { } \ No newline at end of file diff --git a/src/main/java/com/example/accessingdatamysql/Organization.java b/src/main/java/com/example/accessingdatamysql/org/Organization.java similarity index 81% rename from src/main/java/com/example/accessingdatamysql/Organization.java rename to src/main/java/com/example/accessingdatamysql/org/Organization.java index 98456f10a4e576edd02b401ec44c9f92b916f670..066808fea8442e0ef92f5aad0d65a1ede70c0312 100644 --- a/src/main/java/com/example/accessingdatamysql/Organization.java +++ b/src/main/java/com/example/accessingdatamysql/org/Organization.java @@ -1,4 +1,4 @@ -package com.example.accessingdatamysql; +package com.example.accessingdatamysql.org; import jakarta.persistence.*; @@ -7,17 +7,19 @@ import jakarta.persistence.*; @Table(name = "ORGANIZATION") public class Organization { - // enum Category { - // ACADEMIC, - // RECREATION, - // TECHNOLOGY, - // POLITICS, - // GREEKLIFE - // } + public enum Category { + ACADEMIC, + RECREATION, + TECHNOLOGY, + POLITICS, + + GREEKLIFE + } - // private Category category; + @Enumerated(EnumType.STRING) + private Category category; - private String category; +// private String category; private Integer memberCount; @@ -37,11 +39,11 @@ public class Organization { // this.category = category; // } - public String getCategory() { + public Category getCategory() { return category; } - public void setCategory(String category) { + public void setCategory(Category category) { this.category = category; }