Integrate pgroups for shipment data security

Added `pgroups` to secure and associate data with specific permission groups. Updated backend routers, database models, and API endpoints to handle authorization based on `pgroups`. Adjusted frontend components and hooks to support `pgroups` in data fetching and management workflows.
This commit is contained in:
GotthardG
2025-01-22 22:53:37 +01:00
parent 4a1852882a
commit 173e192fc4
14 changed files with 123 additions and 92 deletions

View File

@ -82,7 +82,7 @@ const App: React.FC = () => {
<Routes>
<Route path="/login" element={<LoginView />} />
<Route path="/" element={<ProtectedRoute element={<HomePage />} />} />
<Route path="/shipments" element={<ProtectedRoute element={<ShipmentView activePgroup={activePgroup} />} />} />
<Route path="/shipments" element={<ProtectedRoute element={<ShipmentView pgroups={pgroups} activePgroup={activePgroup} />} />} />
<Route path="/planning" element={<ProtectedRoute element={<PlanningView />} />} />
<Route path="/results" element={<ProtectedRoute element={<ResultsView />} />} />
</Routes>

View File

@ -35,6 +35,8 @@ import DownloadIcon from '@mui/icons-material/Download';
interface DewarDetailsProps {
dewar: Dewar;
pgroups: string;
activePgroup: string;
trackingNumber: string;
setTrackingNumber: (trackingNumber: string) => void;
initialContacts?: Contact[];
@ -45,7 +47,7 @@ interface DewarDetailsProps {
}
interface NewContact {
id: number;
pgroups: string;
firstName: string;
lastName: string;
phone_number: string;
@ -53,7 +55,7 @@ interface NewContact {
}
interface NewReturnAddress {
id: number;
pgroups: string;
street: string;
city: string;
zipcode: string;
@ -61,6 +63,8 @@ interface NewReturnAddress {
}
const DewarDetails: React.FC<DewarDetailsProps> = ({
pgroups,
activePgroup,
dewar,
trackingNumber,
setTrackingNumber,
@ -80,6 +84,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const [puckStatuses, setPuckStatuses] = useState<string[][]>([]);
const [newContact, setNewContact] = useState<NewContact>({
id: 0,
pgroups: activePgroup,
firstName: '',
lastName: '',
phone_number: '',
@ -87,6 +92,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
});
const [newReturnAddress, setNewReturnAddress] = useState<NewReturnAddress>({
id: 0,
pgroups: activePgroup,
street: '',
city: '',
zipcode: '',
@ -166,8 +172,8 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
useEffect(() => {
const getContacts = async () => {
try {
const c = await ContactsService.getContactsContactsGet();
setContactPersons(c);
const c = await ContactsService.getContactsProtectedContactsGet(activePgroup);
setContacts(c);
} catch {
setFeedbackMessage('Failed to load contact persons. Please try again later.');
setOpenSnackbar(true);
@ -176,7 +182,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const getReturnAddresses = async () => {
try {
const a = await AddressesService.getReturnAddressesAddressesGet();
const a = await AddressesService.getReturnAddressesProtectedAddressesGet(activePgroup);
setReturnAddresses(a);
} catch {
setFeedbackMessage('Failed to load return addresses. Please try again later.');
@ -192,7 +198,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const fetchSamples = async () => {
if (dewar.id) {
try {
const fetchedSamples = await ShipmentsService.getSamplesInDewarShipmentsShipmentsShipmentIdDewarsDewarIdSamplesGet(
const fetchedSamples = await ShipmentsService.getSamplesInDewarProtectedShipmentsShipmentsShipmentIdDewarsDewarIdSamplesGet(
shipmentId,
dewar.id
);
@ -276,31 +282,32 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
};
const handleAddContact = async () => {
if (!validateEmail(newContactPerson.email) || !validatePhoneNumber(newContactPerson.phone_number) || !newContactPerson.firstName || !newContactPerson.lastName) {
if (!validateEmail(newContact.email) || !validatePhoneNumber(newContact.phone_number) || !newContact.firstName || !newContact.lastName) {
setFeedbackMessage('Please fill in all new contact person fields correctly.');
setOpenSnackbar(true);
return;
}
const payload = {
firstname: newContactPerson.firstName,
lastname: newContactPerson.lastName,
phone_number: newContactPerson.phone_number,
email: newContactPerson.email,
pgroups: activePgroup,
firstname: newContact.firstName,
lastname: newContact.lastName,
phone_number: newContact.phone_number,
email: newContact.email,
};
try {
const c = await ContactsService.createContactContactsPost(payload);
setContactPersons([...contactPersons, c]);
const c = await ContactsService.createContactProtectedContactsPost(payload);
setContacts([...contacts, c]);
setFeedbackMessage('Contact person added successfully.');
setNewContactPerson({ id: 0, firstName: '', lastName: '', phone_number: '', email: '' });
setSelectedContactPerson(c.id?.toString() || '');
setNewContact({ pgroups: activePgroup, firstName: '', lastName: '', phone_number: '', email: '' });
setSelectedContact(c.id?.toString() || '');
} catch {
setFeedbackMessage('Failed to create a new contact person. Please try again later.');
}
setOpenSnackbar(true);
setIsCreatingContactPerson(false);
setIsCreatingContact(false);
setChangesMade(true);
};
@ -312,6 +319,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
}
const payload = {
pgroups: activePgroup,
street: newReturnAddress.street.trim(),
city: newReturnAddress.city.trim(),
zipcode: newReturnAddress.zipcode.trim(),
@ -319,11 +327,11 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
};
try {
const a = await AddressesService.createReturnAddressAddressesPost(payload);
const a = await AddressesService.createReturnAddressProtectedAddressesPost(payload);
setReturnAddresses([...returnAddresses, a]);
setFeedbackMessage('Return address added successfully.');
setNewReturnAddress({
id: 0,
pgroups: activePgroup,
street: '',
city: '',
zipcode: '',
@ -347,7 +355,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
return date.toISOString().split('T')[0];
};
if (!selectedContactPerson || !selectedReturnAddress) {
if (!selectedContact || !selectedReturnAddress) {
setFeedbackMessage('Please ensure all required fields are filled.');
setOpenSnackbar(true);
return;
@ -375,7 +383,7 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
arrival_date: dewar.arrival_date,
returning_date: dewar.returning_date,
return_address_id: parseInt(selectedReturnAddress ?? '', 10),
contact_id: parseInt(selectedContactPerson ?? '', 10),
contact_id: parseInt(selectedContact ?? '', 10),
};
await DewarsService.updateDewarDewarsDewarIdPut(dewarId, payload);
@ -554,30 +562,30 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
<Typography variant="body1">Current Contact Person:</Typography>
<Select
value={selectedContactPerson}
value={selectedContact}
onChange={(e) => {
const value = e.target.value;
setSelectedContactPerson(value);
setIsCreatingContactPerson(value === 'add');
setSelectedContact(value);
setIsCreatingContact(value === 'add');
setChangesMade(true);
}}
displayEmpty
sx={{ width: '300px', marginBottom: 2 }}
>
{contactPersons.map((person) => (
{contacts.map((person) => (
<MenuItem key={person.id} value={person.id?.toString()}>
{person.firstname} {person.lastname}
</MenuItem>
))}
<MenuItem value="add">Add New Contact Person</MenuItem>
</Select>
{isCreatingContactPerson && (
{isCreatingContact && (
<Box sx={{ marginBottom: 2 }}>
<TextField
label="First Name"
value={newContactPerson.firstName}
value={newContact.firstName}
onChange={(e) =>
setNewContactPerson((prev) => ({
setNewContact((prev) => ({
...prev,
firstName: e.target.value,
}))
@ -587,9 +595,9 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
/>
<TextField
label="Last Name"
value={newContactPerson.lastName}
value={newContact.lastName}
onChange={(e) =>
setNewContactPerson((prev) => ({
setNewContact((prev) => ({
...prev,
lastName: e.target.value,
}))
@ -599,9 +607,9 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
/>
<TextField
label="Phone Number"
value={newContactPerson.phone_number}
value={newContact.phone_number}
onChange={(e) =>
setNewContactPerson((prev) => ({
setNewContact((prev) => ({
...prev,
phone_number: e.target.value,
}))
@ -611,9 +619,9 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
/>
<TextField
label="Email"
value={newContactPerson.email}
value={newContact.email}
onChange={(e) =>
setNewContactPerson((prev) => ({
setNewContact((prev) => ({
...prev,
email: e.target.value,
}))

View File

@ -13,6 +13,7 @@ const MAX_COMMENTS_LENGTH = 200;
interface ShipmentDetailsProps {
activePgroup: string;
pgroups: string;
isCreatingShipment: boolean;
sx?: SxProps;
selectedShipment: Shipment | null;
@ -24,6 +25,7 @@ interface ShipmentDetailsProps {
}
const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
pgroups,
activePgroup,
sx,
selectedShipment,
@ -82,7 +84,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
const confirmed = window.confirm('Are you sure you want to delete this dewar?');
if (confirmed && selectedShipment) {
try {
const updatedShipment = await ShipmentsService.removeDewarFromShipmentShipmentsShipmentIdRemoveDewarDewarIdDelete(
const updatedShipment = await ShipmentsService.removeDewarFromShipmentProtectedShipmentsShipmentIdRemoveDewarDewarIdDelete(
selectedShipment.id,
dewarId
);
@ -133,7 +135,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
const createdDewar = await DewarsService.createOrUpdateDewarDewarsPost(selectedShipment.id, newDewarToPost);
if (createdDewar && selectedShipment) {
const updatedShipment = await ShipmentsService.addDewarToShipmentShipmentsShipmentIdAddDewarPost(selectedShipment.id, createdDewar.id);
const updatedShipment = await ShipmentsService.addDewarToShipmentProtectedShipmentsShipmentIdAddDewarPost(selectedShipment.id, createdDewar.id);
setSelectedShipment(updatedShipment);
setIsAddingDewar(false);
setNewDewar(initialNewDewarState);
@ -159,7 +161,7 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
const payload = { comments };
// Assuming `updateShipmentCommentsShipmentsShipmentIdCommentsPut` only needs the shipment ID
const updatedShipment = await ShipmentsService.updateShipmentCommentsShipmentsShipmentIdCommentsPut(selectedShipment.id, payload);
const updatedShipment = await ShipmentsService.updateShipmentCommentsProtectedShipmentsShipmentIdCommentsPut(selectedShipment.id, payload);
setSelectedShipment({ ...selectedShipment, comments: updatedShipment.comments });
setInitialComments(comments);
@ -350,6 +352,8 @@ const ShipmentDetails: React.FC<ShipmentDetailsProps> = ({
{localSelectedDewar?.id === dewar.id && (
<DewarDetails
pgroups={pgroups}
activePgroup={activePgroup}
dewar={localSelectedDewar}
trackingNumber={localSelectedDewar?.tracking_number || ''}
setTrackingNumber={(value) => {

View File

@ -11,8 +11,6 @@ import {
} from '../../openapi';
import { useEffect } from 'react';
import { CountryList } from './CountryList'; // Import the list of countries
import { jwtDecode } from 'jwt-decode';
const MAX_COMMENTS_LENGTH = 200;
@ -206,13 +204,13 @@ const ShipmentForm: React.FC<ShipmentFormProps> = ({
return_address_id: selectedReturnAddressId!,
proposal_id: selectedProposalId!,
dewars: newShipment.dewars || [],
//pgroup: activePgroup,
pgroups: activePgroup,
};
console.log('Shipment Payload being sent:', payload);
try {
await ShipmentsService.createShipmentShipmentsPost(payload);
await ShipmentsService.createShipmentProtectedShipmentsPost(payload);
setErrorMessage(null);
refreshShipments();
onCancel();

View File

@ -58,7 +58,7 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
if (!shipmentId) return;
try {
await ShipmentsService.deleteShipmentShipmentsShipmentIdDelete(shipmentId);
await ShipmentsService.deleteShipmentProtectedShipmentsShipmentIdDelete(shipmentId);
refreshShipments();
selectShipment(null);
alert("Shipment deleted successfully.");

View File

@ -1,14 +1,14 @@
import { useState, useEffect } from 'react';
import { ShipmentsService, Shipment, Contact } from '../../openapi';
const useShipments = () => {
const useShipments = (activePgroup: string) => {
const [shipments, setShipments] = useState<Shipment[]>([]);
const [error, setError] = useState<string | null>(null);
const [defaultContact, setDefaultContact] = useState<Contact | undefined>();
const fetchAndSetShipments = async () => {
try {
const shipmentsData = await ShipmentsService.fetchShipmentsShipmentsGet();
const shipmentsData = await ShipmentsService.fetchShipmentsProtectedShipmentsGet(activePgroup);
setShipments(shipmentsData);
} catch (error) {
console.error('Failed to fetch shipments:', error);
@ -18,7 +18,7 @@ const useShipments = () => {
const fetchDefaultContact = async () => {
try {
const contacts = await ShipmentsService.getShipmentContactPersonsShipmentsContactPersonsGet();
const contacts = await ShipmentsService.getShipmentContactPersonsProtectedShipmentsContactPersonsGet();
setDefaultContact(contacts[0]);
} catch (error) {
console.error('Failed to fetch contact persons:', error);
@ -27,9 +27,11 @@ const useShipments = () => {
};
useEffect(() => {
fetchAndSetShipments();
fetchDefaultContact();
}, []);
if (activePgroup) {
fetchAndSetShipments();
fetchDefaultContact();
}
}, [activePgroup]); // Refetch shipments when activePgroup changes
return { shipments, error, defaultContact, fetchAndSetShipments };
};

View File

@ -1,17 +1,18 @@
import React, { useState, useEffect } from 'react';
import React, {useState, useEffect} from 'react';
import ShipmentPanel from '../components/ShipmentPanel';
import ShipmentDetails from '../components/ShipmentDetails';
import ShipmentForm from '../components/ShipmentForm';
import { Dewar, OpenAPI, Shipment } from '../../openapi';
import {Dewar, OpenAPI, Shipment} from '../../openapi';
import useShipments from '../hooks/useShipments';
import { Grid, Container } from '@mui/material';
import {Grid, Container} from '@mui/material';
type ShipmentViewProps = {
activePgroup: string;
activePgroup: string,
pgroups: string,
};
const ShipmentView: React.FC<ShipmentViewProps> = ( { activePgroup }) => {
const { shipments, error, defaultContact, fetchAndSetShipments } = useShipments();
const ShipmentView: React.FC<ShipmentViewProps> = ({activePgroup, pgroups}) => {
const { shipments, error, defaultContact, fetchAndSetShipments } = useShipments(activePgroup);
const [selectedShipment, setSelectedShipment] = useState<Shipment | null>(null);
const [selectedDewar, setSelectedDewar] = useState<Dewar | null>(null);
const [isCreatingShipment, setIsCreatingShipment] = useState(false);
@ -29,8 +30,8 @@ const ShipmentView: React.FC<ShipmentViewProps> = ( { activePgroup }) => {
mode === 'test'
? import.meta.env.VITE_OPENAPI_BASE_TEST
: mode === 'prod'
? import.meta.env.VITE_OPENAPI_BASE_PROD
: import.meta.env.VITE_OPENAPI_BASE_DEV;
? import.meta.env.VITE_OPENAPI_BASE_PROD
: import.meta.env.VITE_OPENAPI_BASE_DEV;
// Log warning if `OpenAPI.BASE` is unresolved
if (!OpenAPI.BASE) {
@ -68,6 +69,7 @@ const ShipmentView: React.FC<ShipmentViewProps> = ( { activePgroup }) => {
if (selectedShipment) {
return (
<ShipmentDetails
pgroups={pgroups}
activePgroup={activePgroup}
isCreatingShipment={isCreatingShipment}
sx={{ flexGrow: 1 }}