mirror of
https://github.com/philipredstone/relnet.git
synced 2025-06-17 21:11:16 +02:00
add prettifier
This commit is contained in:
parent
c078610c4d
commit
eceacf2117
19
.prettierignore
Normal file
19
.prettierignore
Normal file
@ -0,0 +1,19 @@
|
||||
# Ignore build outputs
|
||||
/dist
|
||||
/build
|
||||
|
||||
# Ignore dependencies
|
||||
/node_modules
|
||||
|
||||
# Ignore coverage reports
|
||||
/coverage
|
||||
|
||||
# Ignore logs
|
||||
*.log
|
||||
|
||||
# Ignore frontend (it has its own Prettier config)
|
||||
/frontend
|
||||
|
||||
# Ignore configuration files
|
||||
.env
|
||||
.env.*
|
10
.prettierrc
Normal file
10
.prettierrc
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "lf",
|
||||
"bracketSpacing": true
|
||||
}
|
28
.vscode/settings.json
vendored
Normal file
28
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": "explicit"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
16
frontend/.prettierignore
Normal file
16
frontend/.prettierignore
Normal file
@ -0,0 +1,16 @@
|
||||
# Ignore build outputs
|
||||
/dist
|
||||
/build
|
||||
|
||||
# Ignore dependencies
|
||||
/node_modules
|
||||
|
||||
# Ignore coverage reports
|
||||
/coverage
|
||||
|
||||
# Ignore logs
|
||||
*.log
|
||||
|
||||
# Ignore configuration files
|
||||
.env
|
||||
.env.*
|
12
frontend/.prettierrc
Normal file
12
frontend/.prettierrc
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "lf",
|
||||
"bracketSpacing": true,
|
||||
"jsxSingleQuote": false,
|
||||
"bracketSameLine": false
|
||||
}
|
2853
frontend/package-lock.json
generated
Normal file
2853
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,9 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"format": "prettier --write \"src/**/*.{tsx,ts,js,jsx,json,css,html}\"",
|
||||
"format:check": "prettier --check \"src/**/*.{tsx,ts,js,jsx,json,css,html}\""
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
@ -22,6 +24,7 @@
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@vitejs/plugin-react": "^4.4.0",
|
||||
"prettier": "^3.5.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.2.6",
|
||||
|
@ -42,19 +42,27 @@ export const getUserNetworks = async (): Promise<Network[]> => {
|
||||
|
||||
// Create a new network
|
||||
export const createNetwork = async (data: CreateNetworkData): Promise<Network> => {
|
||||
const response = await axios.post<{ success: boolean; data: Network }>(`${API_URL}/networks`, data);
|
||||
const response = await axios.post<{ success: boolean; data: Network }>(
|
||||
`${API_URL}/networks`,
|
||||
data
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
// Get a specific network
|
||||
export const getNetwork = async (id: string): Promise<Network> => {
|
||||
const response = await axios.get<{ success: boolean; data: Network }>(`${API_URL}/networks/${id}`);
|
||||
const response = await axios.get<{ success: boolean; data: Network }>(
|
||||
`${API_URL}/networks/${id}`
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
// Update a network
|
||||
export const updateNetwork = async (id: string, data: UpdateNetworkData): Promise<Network> => {
|
||||
const response = await axios.put<{ success: boolean; data: Network }>(`${API_URL}/networks/${id}`, data);
|
||||
const response = await axios.put<{ success: boolean; data: Network }>(
|
||||
`${API_URL}/networks/${id}`,
|
||||
data
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
|
@ -64,6 +64,9 @@ export const updateRelationship = async (
|
||||
};
|
||||
|
||||
// Remove a relationship
|
||||
export const removeRelationship = async (networkId: string, relationshipId: string): Promise<void> => {
|
||||
export const removeRelationship = async (
|
||||
networkId: string,
|
||||
relationshipId: string
|
||||
): Promise<void> => {
|
||||
await axios.delete(`${API_URL}/networks/${networkId}/relationships/${relationshipId}`);
|
||||
};
|
@ -18,7 +18,7 @@ const FriendshipNetwork: React.FC = () => {
|
||||
deletePerson,
|
||||
createRelationship,
|
||||
updateRelationship,
|
||||
deleteRelationship
|
||||
deleteRelationship,
|
||||
} = useFriendshipNetwork(id || null);
|
||||
|
||||
// Local state for the UI
|
||||
@ -27,13 +27,13 @@ const FriendshipNetwork: React.FC = () => {
|
||||
const [newPerson, setNewPerson] = useState({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
birthday: ''
|
||||
birthday: '',
|
||||
});
|
||||
const [newRelationship, setNewRelationship] = useState({
|
||||
source: '',
|
||||
targets: [] as string[],
|
||||
type: 'freund',
|
||||
customType: ''
|
||||
customType: '',
|
||||
});
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
const nodeRefs = useRef<{ [key: string]: SVGGElement | null }>({});
|
||||
@ -67,14 +67,14 @@ const FriendshipNetwork: React.FC = () => {
|
||||
birthday: newPerson.birthday || undefined,
|
||||
position: {
|
||||
x: 100 + Math.random() * 400,
|
||||
y: 100 + Math.random() * 300
|
||||
}
|
||||
y: 100 + Math.random() * 300,
|
||||
},
|
||||
});
|
||||
|
||||
setNewPerson({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
birthday: ''
|
||||
birthday: '',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error adding person:', error);
|
||||
@ -102,7 +102,8 @@ const FriendshipNetwork: React.FC = () => {
|
||||
const existingRelationships: any[] = [];
|
||||
targets.forEach(target => {
|
||||
if (source !== target) {
|
||||
const existingEdge = relationships.find(edge =>
|
||||
const existingEdge = relationships.find(
|
||||
edge =>
|
||||
(edge.source === source && edge.target === target) ||
|
||||
(edge.source === target && edge.target === source)
|
||||
);
|
||||
@ -113,7 +114,7 @@ const FriendshipNetwork: React.FC = () => {
|
||||
target,
|
||||
existingType: existingEdge.type,
|
||||
newType: actualType,
|
||||
edgeId: existingEdge.id
|
||||
edgeId: existingEdge.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -123,26 +124,30 @@ const FriendshipNetwork: React.FC = () => {
|
||||
// Show override modal
|
||||
setOverrideRelationship({
|
||||
existingRelationships,
|
||||
newRelationships: targets.filter(target =>
|
||||
source !== target && !existingRelationships.some(rel => rel.target === target)
|
||||
).map(target => ({ source, target, type: actualType }))
|
||||
newRelationships: targets
|
||||
.filter(
|
||||
target => source !== target && !existingRelationships.some(rel => rel.target === target)
|
||||
)
|
||||
.map(target => ({ source, target, type: actualType })),
|
||||
});
|
||||
setShowOverrideModal(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Process each target for new relationships
|
||||
const addPromises = targets.map(target => {
|
||||
const addPromises = targets
|
||||
.map(target => {
|
||||
if (source !== target) {
|
||||
return createRelationship({
|
||||
source,
|
||||
target,
|
||||
type: type as any,
|
||||
customType: type === 'custom' ? customType : undefined
|
||||
customType: type === 'custom' ? customType : undefined,
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
}).filter(Boolean);
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (addPromises.length === 0) {
|
||||
alert('No valid relationships to add.');
|
||||
@ -169,24 +174,28 @@ const FriendshipNetwork: React.FC = () => {
|
||||
await Promise.all(existingRelationships.map(rel => deleteRelationship(rel.edgeId)));
|
||||
|
||||
// Add new overridden relationships
|
||||
await Promise.all(existingRelationships.map(rel =>
|
||||
await Promise.all(
|
||||
existingRelationships.map(rel =>
|
||||
createRelationship({
|
||||
source: rel.source,
|
||||
target: rel.target,
|
||||
type: rel.newType as any,
|
||||
customType: rel.newType === 'custom' ? rel.customType : undefined
|
||||
customType: rel.newType === 'custom' ? rel.customType : undefined,
|
||||
})
|
||||
));
|
||||
)
|
||||
);
|
||||
|
||||
// Add completely new relationships
|
||||
await Promise.all(newRelationships.map(rel =>
|
||||
await Promise.all(
|
||||
newRelationships.map(rel =>
|
||||
createRelationship({
|
||||
source: rel.source,
|
||||
target: rel.target,
|
||||
type: rel.type as any,
|
||||
customType: rel.type === 'custom' ? rel.customType : undefined
|
||||
customType: rel.type === 'custom' ? rel.customType : undefined,
|
||||
})
|
||||
));
|
||||
)
|
||||
);
|
||||
|
||||
setShowOverrideModal(false);
|
||||
setOverrideRelationship(null);
|
||||
@ -202,14 +211,16 @@ const FriendshipNetwork: React.FC = () => {
|
||||
// If there are new relationships that don't need overrides, add those
|
||||
if (overrideRelationship && overrideRelationship.newRelationships.length > 0) {
|
||||
try {
|
||||
await Promise.all(overrideRelationship.newRelationships.map(rel =>
|
||||
await Promise.all(
|
||||
overrideRelationship.newRelationships.map(rel =>
|
||||
createRelationship({
|
||||
source: rel.source,
|
||||
target: rel.target,
|
||||
type: rel.type as any,
|
||||
customType: rel.type === 'custom' ? rel.customType : undefined
|
||||
customType: rel.type === 'custom' ? rel.customType : undefined,
|
||||
})
|
||||
));
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error adding new relationships:', error);
|
||||
}
|
||||
@ -222,7 +233,7 @@ const FriendshipNetwork: React.FC = () => {
|
||||
// Handle multiple selections in the targets dropdown
|
||||
const handleTargetChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const selectedOptions = Array.from(e.target.selectedOptions, option => option.value);
|
||||
setNewRelationship({...newRelationship, targets: selectedOptions});
|
||||
setNewRelationship({ ...newRelationship, targets: selectedOptions });
|
||||
};
|
||||
|
||||
// Handle node drag start
|
||||
@ -254,7 +265,7 @@ const FriendshipNetwork: React.FC = () => {
|
||||
node.id === dragging
|
||||
? {
|
||||
...node,
|
||||
position: { x: newX, y: newY }
|
||||
position: { x: newX, y: newY },
|
||||
}
|
||||
: node
|
||||
);
|
||||
@ -299,7 +310,11 @@ const FriendshipNetwork: React.FC = () => {
|
||||
|
||||
// Delete a node and its associated edges
|
||||
const handleDeleteNode = async (id: string) => {
|
||||
if (window.confirm('Are you sure you want to delete this person? All their relationships will also be deleted.')) {
|
||||
if (
|
||||
window.confirm(
|
||||
'Are you sure you want to delete this person? All their relationships will also be deleted.'
|
||||
)
|
||||
) {
|
||||
try {
|
||||
await deletePerson(id);
|
||||
setSelectedNode(null);
|
||||
@ -313,12 +328,17 @@ const FriendshipNetwork: React.FC = () => {
|
||||
|
||||
// Get relationship type label
|
||||
const getRelationshipLabel = (type: string) => {
|
||||
switch(type) {
|
||||
case 'freund': return 'Freund/in';
|
||||
case 'partner': return 'Partner/in';
|
||||
case 'familie': return 'Familie/Verwandschaft';
|
||||
case 'arbeitskolleg': return 'Arbeitskolleg/innen';
|
||||
default: return type;
|
||||
switch (type) {
|
||||
case 'freund':
|
||||
return 'Freund/in';
|
||||
case 'partner':
|
||||
return 'Partner/in';
|
||||
case 'familie':
|
||||
return 'Familie/Verwandschaft';
|
||||
case 'arbeitskolleg':
|
||||
return 'Arbeitskolleg/innen';
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
@ -339,13 +359,13 @@ const FriendshipNetwork: React.FC = () => {
|
||||
return {
|
||||
person: other ? `${other.firstName} ${other.lastName}` : otherId,
|
||||
type: edge.type,
|
||||
edgeId: edge.id
|
||||
edgeId: edge.id,
|
||||
};
|
||||
});
|
||||
|
||||
setPopupInfo({
|
||||
...popupInfo,
|
||||
relationships: nodeRelationships
|
||||
relationships: nodeRelationships,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@ -360,22 +380,22 @@ const FriendshipNetwork: React.FC = () => {
|
||||
if (!node) return;
|
||||
|
||||
// Find all relationships
|
||||
const nodeRelationships = relationships.filter(
|
||||
edge => edge.source === nodeId || edge.target === nodeId
|
||||
).map(edge => {
|
||||
const nodeRelationships = relationships
|
||||
.filter(edge => edge.source === nodeId || edge.target === nodeId)
|
||||
.map(edge => {
|
||||
const otherId = edge.source === nodeId ? edge.target : edge.source;
|
||||
const other = people.find(n => n.id === otherId);
|
||||
return {
|
||||
person: other ? `${other.firstName} ${other.lastName}` : otherId,
|
||||
type: edge.type,
|
||||
edgeId: edge.id
|
||||
edgeId: edge.id,
|
||||
};
|
||||
});
|
||||
|
||||
setPopupInfo({
|
||||
node,
|
||||
relationships: nodeRelationships,
|
||||
position: { ...node.position }
|
||||
position: { ...node.position },
|
||||
});
|
||||
};
|
||||
|
||||
@ -392,9 +412,7 @@ const FriendshipNetwork: React.FC = () => {
|
||||
// Close popup when clicking outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (popupInfo &&
|
||||
!(e.target as Element).closest('.popup') &&
|
||||
!dragging) {
|
||||
if (popupInfo && !(e.target as Element).closest('.popup') && !dragging) {
|
||||
closePopup();
|
||||
}
|
||||
};
|
||||
@ -410,16 +428,16 @@ const FriendshipNetwork: React.FC = () => {
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div className="bg-red-100 border border-red-400 text-red-700 p-4 m-4 rounded">{error}</div>;
|
||||
return (
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 p-4 m-4 rounded">{error}</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-gray-100">
|
||||
{/* Sidebar menu */}
|
||||
<div className="w-64 bg-white p-4 border-r border-gray-200 overflow-y-auto">
|
||||
<h2 className="text-xl font-bold mb-4">
|
||||
{currentNetwork?.name || 'Friend Network'}
|
||||
</h2>
|
||||
<h2 className="text-xl font-bold mb-4">{currentNetwork?.name || 'Friend Network'}</h2>
|
||||
|
||||
{/* Add Person Form */}
|
||||
<div className="mb-6">
|
||||
@ -430,21 +448,21 @@ const FriendshipNetwork: React.FC = () => {
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded mb-2"
|
||||
placeholder="First Name"
|
||||
value={newPerson.firstName}
|
||||
onChange={e => setNewPerson({...newPerson, firstName: e.target.value})}
|
||||
onChange={e => setNewPerson({ ...newPerson, firstName: e.target.value })}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded mb-2"
|
||||
placeholder="Last Name"
|
||||
value={newPerson.lastName}
|
||||
onChange={e => setNewPerson({...newPerson, lastName: e.target.value})}
|
||||
onChange={e => setNewPerson({ ...newPerson, lastName: e.target.value })}
|
||||
/>
|
||||
<input
|
||||
type="date"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded mb-2"
|
||||
placeholder="Birthday (Optional)"
|
||||
value={newPerson.birthday}
|
||||
onChange={e => setNewPerson({...newPerson, birthday: e.target.value})}
|
||||
onChange={e => setNewPerson({ ...newPerson, birthday: e.target.value })}
|
||||
/>
|
||||
<button
|
||||
className="w-full bg-blue-500 text-white px-3 py-2 rounded"
|
||||
@ -462,7 +480,7 @@ const FriendshipNetwork: React.FC = () => {
|
||||
<select
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded mb-2"
|
||||
value={newRelationship.source}
|
||||
onChange={e => setNewRelationship({...newRelationship, source: e.target.value})}
|
||||
onChange={e => setNewRelationship({ ...newRelationship, source: e.target.value })}
|
||||
>
|
||||
<option value="">Select first person</option>
|
||||
{people.map(node => (
|
||||
@ -490,7 +508,7 @@ const FriendshipNetwork: React.FC = () => {
|
||||
<select
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded mb-2"
|
||||
value={newRelationship.type}
|
||||
onChange={e => setNewRelationship({...newRelationship, type: e.target.value})}
|
||||
onChange={e => setNewRelationship({ ...newRelationship, type: e.target.value })}
|
||||
>
|
||||
<option value="freund">Freund/in</option>
|
||||
<option value="partner">Partner/in</option>
|
||||
@ -505,7 +523,9 @@ const FriendshipNetwork: React.FC = () => {
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded mb-2"
|
||||
placeholder="Enter custom relationship type"
|
||||
value={newRelationship.customType}
|
||||
onChange={e => setNewRelationship({...newRelationship, customType: e.target.value})}
|
||||
onChange={e =>
|
||||
setNewRelationship({ ...newRelationship, customType: e.target.value })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -524,7 +544,9 @@ const FriendshipNetwork: React.FC = () => {
|
||||
<ul className="divide-y divide-gray-200">
|
||||
{people.map(node => (
|
||||
<li key={node.id} className="py-2 flex justify-between items-center">
|
||||
<span>{node.firstName} {node.lastName}</span>
|
||||
<span>
|
||||
{node.firstName} {node.lastName}
|
||||
</span>
|
||||
<button
|
||||
className="text-red-500 hover:text-red-700"
|
||||
onClick={() => handleDeleteNode(node.id)}
|
||||
@ -552,10 +574,13 @@ const FriendshipNetwork: React.FC = () => {
|
||||
<li key={edge.id} className="py-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span>
|
||||
{source.firstName} {source.lastName.charAt(0)}. ↔ {target.firstName} {target.lastName.charAt(0)}.
|
||||
{source.firstName} {source.lastName.charAt(0)}. ↔ {target.firstName}{' '}
|
||||
{target.lastName.charAt(0)}.
|
||||
</span>
|
||||
<div className="flex items-center">
|
||||
<span className="text-xs text-gray-600 mr-2">{getRelationshipLabel(edge.type)}</span>
|
||||
<span className="text-xs text-gray-600 mr-2">
|
||||
{getRelationshipLabel(edge.type)}
|
||||
</span>
|
||||
<button
|
||||
className="text-red-500 hover:text-red-700"
|
||||
onClick={() => handleRemoveRelationship(edge.id)}
|
||||
@ -625,7 +650,9 @@ const FriendshipNetwork: React.FC = () => {
|
||||
key={node.id}
|
||||
transform={`translate(${node.position.x}, ${node.position.y})`}
|
||||
onMouseDown={e => handleMouseDown(e, node.id)}
|
||||
ref={el => { nodeRefs.current[node.id] = el; }}
|
||||
ref={el => {
|
||||
nodeRefs.current[node.id] = el;
|
||||
}}
|
||||
className="cursor-grab"
|
||||
>
|
||||
<circle
|
||||
@ -653,25 +680,30 @@ const FriendshipNetwork: React.FC = () => {
|
||||
<div
|
||||
className="popup absolute bg-white border border-gray-300 rounded shadow-lg p-4 z-10 w-64"
|
||||
style={{
|
||||
left: popupInfo.position.x > (svgRef.current?.clientWidth || 0) / 2
|
||||
? popupInfo.position.x - 260 : popupInfo.position.x + 40,
|
||||
top: popupInfo.position.y > (svgRef.current?.clientHeight || 0) / 2
|
||||
? popupInfo.position.y - 200 : popupInfo.position.y,
|
||||
left:
|
||||
popupInfo.position.x > (svgRef.current?.clientWidth || 0) / 2
|
||||
? popupInfo.position.x - 260
|
||||
: popupInfo.position.x + 40,
|
||||
top:
|
||||
popupInfo.position.y > (svgRef.current?.clientHeight || 0) / 2
|
||||
? popupInfo.position.y - 200
|
||||
: popupInfo.position.y,
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<h3 className="text-lg font-bold">Person Details</h3>
|
||||
<button
|
||||
className="text-gray-500 hover:text-gray-700"
|
||||
onClick={closePopup}
|
||||
>
|
||||
<button className="text-gray-500 hover:text-gray-700" onClick={closePopup}>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<p className="font-semibold">Name: {popupInfo.node.firstName} {popupInfo.node.lastName}</p>
|
||||
<p className="font-semibold">
|
||||
Name: {popupInfo.node.firstName} {popupInfo.node.lastName}
|
||||
</p>
|
||||
{popupInfo.node.birthday && (
|
||||
<p className="text-sm text-gray-600">Birthday: {new Date(popupInfo.node.birthday).toLocaleDateString()}</p>
|
||||
<p className="text-sm text-gray-600">
|
||||
Birthday: {new Date(popupInfo.node.birthday).toLocaleDateString()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
@ -709,8 +741,8 @@ const FriendshipNetwork: React.FC = () => {
|
||||
<h3 className="text-xl font-bold mb-4">Existing Relationship(s)</h3>
|
||||
<p className="mb-4">
|
||||
{overrideRelationship.existingRelationships.length === 1
|
||||
? "There is already a relationship between these people:"
|
||||
: "There are already relationships between these people:"}
|
||||
? 'There is already a relationship between these people:'
|
||||
: 'There are already relationships between these people:'}
|
||||
</p>
|
||||
|
||||
<ul className="mb-4 text-sm border rounded divide-y">
|
||||
@ -723,7 +755,8 @@ const FriendshipNetwork: React.FC = () => {
|
||||
<li key={index} className="p-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span>
|
||||
{source.firstName} {source.lastName} ↔ {target.firstName} {target.lastName}
|
||||
{source.firstName} {source.lastName} ↔ {target.firstName}{' '}
|
||||
{target.lastName}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-xs mt-1">
|
||||
@ -761,7 +794,10 @@ const FriendshipNetwork: React.FC = () => {
|
||||
|
||||
{/* Instructions */}
|
||||
<div className="absolute bottom-4 left-4 right-4 bg-white border border-gray-200 rounded p-2 text-sm">
|
||||
<p><strong>Tip:</strong> Drag people to arrange them. Click on a person to view their details and relationships.</p>
|
||||
<p>
|
||||
<strong>Tip:</strong> Drag people to arrange them. Click on a person to view their
|
||||
details and relationships.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -46,7 +46,7 @@ const Login: React.FC = () => {
|
||||
type="email"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@ -60,7 +60,7 @@ const Login: React.FC = () => {
|
||||
type="password"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
@ -60,7 +60,7 @@ const Register: React.FC = () => {
|
||||
type="text"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
onChange={e => setUsername(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@ -74,7 +74,7 @@ const Register: React.FC = () => {
|
||||
type="email"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@ -88,7 +88,7 @@ const Register: React.FC = () => {
|
||||
type="password"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@ -102,7 +102,7 @@ const Register: React.FC = () => {
|
||||
type="password"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
onChange={e => setConfirmPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import {Link, useNavigate} from 'react-router-dom';
|
||||
import {useAuth} from '../../context/AuthContext';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const {user, logout} = useAuth();
|
||||
const { user, logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogout = async () => {
|
||||
|
@ -29,7 +29,7 @@ const NetworkList: React.FC = () => {
|
||||
const network = await createNetwork({
|
||||
name: newNetworkName.trim(),
|
||||
description: newNetworkDescription.trim() || undefined,
|
||||
isPublic
|
||||
isPublic,
|
||||
});
|
||||
|
||||
// Reset form
|
||||
@ -48,7 +48,9 @@ const NetworkList: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleDeleteNetwork = async (id: string) => {
|
||||
if (window.confirm('Are you sure you want to delete this network? This action cannot be undone.')) {
|
||||
if (
|
||||
window.confirm('Are you sure you want to delete this network? This action cannot be undone.')
|
||||
) {
|
||||
try {
|
||||
await deleteNetwork(id);
|
||||
} catch (err: any) {
|
||||
@ -100,7 +102,7 @@ const NetworkList: React.FC = () => {
|
||||
type="text"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded"
|
||||
value={newNetworkName}
|
||||
onChange={(e) => setNewNetworkName(e.target.value)}
|
||||
onChange={e => setNewNetworkName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@ -113,7 +115,7 @@ const NetworkList: React.FC = () => {
|
||||
id="description"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded"
|
||||
value={newNetworkDescription}
|
||||
onChange={(e) => setNewNetworkDescription(e.target.value)}
|
||||
onChange={e => setNewNetworkDescription(e.target.value)}
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
@ -124,7 +126,7 @@ const NetworkList: React.FC = () => {
|
||||
type="checkbox"
|
||||
className="mr-2"
|
||||
checked={isPublic}
|
||||
onChange={(e) => setIsPublic(e.target.checked)}
|
||||
onChange={e => setIsPublic(e.target.checked)}
|
||||
/>
|
||||
<span className="text-gray-700 text-sm font-bold">Make this network public</span>
|
||||
</label>
|
||||
@ -163,12 +165,11 @@ const NetworkList: React.FC = () => {
|
||||
{networks
|
||||
.filter(network => {
|
||||
if (!user) return false;
|
||||
const ownerId = typeof network.owner === 'string'
|
||||
? network.owner
|
||||
: network.owner._id;
|
||||
const ownerId =
|
||||
typeof network.owner === 'string' ? network.owner : network.owner._id;
|
||||
return ownerId === user.id;
|
||||
})
|
||||
.map((network) => (
|
||||
.map(network => (
|
||||
<div key={network._id} className="bg-white rounded-lg shadow-md overflow-hidden">
|
||||
<div className="p-4">
|
||||
<h2 className="text-xl font-bold mb-2">{network.name}</h2>
|
||||
@ -176,7 +177,9 @@ const NetworkList: React.FC = () => {
|
||||
<p className="text-gray-600 mb-4">{network.description}</p>
|
||||
)}
|
||||
<div className="flex items-center mb-4">
|
||||
<span className={`px-2 py-1 rounded-full text-xs ${network.isPublic ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}>
|
||||
<span
|
||||
className={`px-2 py-1 rounded-full text-xs ${network.isPublic ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}
|
||||
>
|
||||
{network.isPublic ? 'Public' : 'Private'}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500 ml-2">
|
||||
@ -203,9 +206,7 @@ const NetworkList: React.FC = () => {
|
||||
</div>
|
||||
{networks.filter(network => {
|
||||
if (!user) return false;
|
||||
const ownerId = typeof network.owner === 'string'
|
||||
? network.owner
|
||||
: network.owner._id;
|
||||
const ownerId = typeof network.owner === 'string' ? network.owner : network.owner._id;
|
||||
return ownerId === user.id;
|
||||
}).length === 0 && (
|
||||
<p className="text-gray-600 mb-4">You haven't created any networks yet.</p>
|
||||
@ -215,9 +216,7 @@ const NetworkList: React.FC = () => {
|
||||
{/* Public Networks Section */}
|
||||
{networks.some(network => {
|
||||
if (!user) return false;
|
||||
const ownerId = typeof network.owner === 'string'
|
||||
? network.owner
|
||||
: network.owner._id;
|
||||
const ownerId = typeof network.owner === 'string' ? network.owner : network.owner._id;
|
||||
return ownerId !== user.id;
|
||||
}) && (
|
||||
<div>
|
||||
@ -226,13 +225,15 @@ const NetworkList: React.FC = () => {
|
||||
{networks
|
||||
.filter(network => {
|
||||
if (!user) return false;
|
||||
const ownerId = typeof network.owner === 'string'
|
||||
? network.owner
|
||||
: network.owner._id;
|
||||
const ownerId =
|
||||
typeof network.owner === 'string' ? network.owner : network.owner._id;
|
||||
return ownerId !== user.id;
|
||||
})
|
||||
.map((network) => (
|
||||
<div key={network._id} className="bg-white rounded-lg shadow-md overflow-hidden border-l-4 border-green-500">
|
||||
.map(network => (
|
||||
<div
|
||||
key={network._id}
|
||||
className="bg-white rounded-lg shadow-md overflow-hidden border-l-4 border-green-500"
|
||||
>
|
||||
<div className="p-4">
|
||||
<h2 className="text-xl font-bold mb-2">{network.name}</h2>
|
||||
{network.description && (
|
||||
@ -246,9 +247,8 @@ const NetworkList: React.FC = () => {
|
||||
Created: {new Date(network.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500 ml-2">
|
||||
By: {typeof network.owner === 'string'
|
||||
? 'Unknown'
|
||||
: network.owner.username}
|
||||
By:{' '}
|
||||
{typeof network.owner === 'string' ? 'Unknown' : network.owner.username}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
|
@ -1,5 +1,13 @@
|
||||
import React, { createContext, useState, useEffect, useContext, ReactNode } from 'react';
|
||||
import { User, getCurrentUser, login as apiLogin, register as apiRegister, logout as apiLogout, LoginData, RegisterData } from '../api/auth';
|
||||
import {
|
||||
User,
|
||||
getCurrentUser,
|
||||
login as apiLogin,
|
||||
register as apiRegister,
|
||||
logout as apiLogout,
|
||||
LoginData,
|
||||
RegisterData,
|
||||
} from '../api/auth';
|
||||
|
||||
interface AuthContextProps {
|
||||
user: User | null;
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
updateNetwork as apiUpdateNetwork,
|
||||
deleteNetwork as apiDeleteNetwork,
|
||||
CreateNetworkData,
|
||||
UpdateNetworkData
|
||||
UpdateNetworkData,
|
||||
} from '../api/network';
|
||||
import { useAuth } from './AuthContext';
|
||||
|
||||
@ -66,9 +66,7 @@ export const NetworkProvider: React.FC<{ children: ReactNode }> = ({ children })
|
||||
const updateNetwork = async (id: string, data: UpdateNetworkData): Promise<Network> => {
|
||||
try {
|
||||
const updatedNetwork = await apiUpdateNetwork(id, data);
|
||||
setNetworks(networks.map(network =>
|
||||
network._id === id ? updatedNetwork : network
|
||||
));
|
||||
setNetworks(networks.map(network => (network._id === id ? updatedNetwork : network)));
|
||||
return updatedNetwork;
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Failed to update network');
|
||||
@ -99,7 +97,7 @@ export const NetworkProvider: React.FC<{ children: ReactNode }> = ({ children })
|
||||
createNetwork,
|
||||
updateNetwork,
|
||||
deleteNetwork,
|
||||
refreshNetworks
|
||||
refreshNetworks,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Person, getPeople, addPerson, updatePerson, removePerson } from '../api/people';
|
||||
import { Relationship, getRelationships, addRelationship, updateRelationship, removeRelationship } from '../api/relationships';
|
||||
import {
|
||||
Relationship,
|
||||
getRelationships,
|
||||
addRelationship,
|
||||
updateRelationship,
|
||||
removeRelationship,
|
||||
} from '../api/relationships';
|
||||
|
||||
interface PersonNode extends Person {
|
||||
// Additional properties needed for the visualization
|
||||
@ -35,18 +41,18 @@ export const useFriendshipNetwork = (networkId: string | null) => {
|
||||
// Fetch people and relationships in parallel
|
||||
const [peopleData, relationshipsData] = await Promise.all([
|
||||
getPeople(networkId),
|
||||
getRelationships(networkId)
|
||||
getRelationships(networkId),
|
||||
]);
|
||||
|
||||
// Transform to add the id property needed by the visualization
|
||||
const peopleNodes: PersonNode[] = peopleData.map(person => ({
|
||||
...person,
|
||||
id: person._id
|
||||
id: person._id,
|
||||
}));
|
||||
|
||||
const relationshipEdges: RelationshipEdge[] = relationshipsData.map(rel => ({
|
||||
...rel,
|
||||
id: rel._id
|
||||
id: rel._id,
|
||||
}));
|
||||
|
||||
setPeople(peopleNodes);
|
||||
@ -68,7 +74,7 @@ export const useFriendshipNetwork = (networkId: string | null) => {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
birthday?: string;
|
||||
position?: { x: number; y: number }
|
||||
position?: { x: number; y: number };
|
||||
}): Promise<PersonNode> => {
|
||||
if (!networkId) throw new Error('No network selected');
|
||||
|
||||
@ -90,7 +96,7 @@ export const useFriendshipNetwork = (networkId: string | null) => {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
birthday?: string | null;
|
||||
position?: { x: number; y: number }
|
||||
position?: { x: number; y: number };
|
||||
}
|
||||
): Promise<PersonNode> => {
|
||||
if (!networkId) throw new Error('No network selected');
|
||||
@ -99,9 +105,7 @@ export const useFriendshipNetwork = (networkId: string | null) => {
|
||||
const updatedPerson = await updatePerson(networkId, personId, personData);
|
||||
const updatedPersonNode: PersonNode = { ...updatedPerson, id: updatedPerson._id };
|
||||
|
||||
setPeople(people.map(person =>
|
||||
person._id === personId ? updatedPersonNode : person
|
||||
));
|
||||
setPeople(people.map(person => (person._id === personId ? updatedPersonNode : person)));
|
||||
|
||||
return updatedPersonNode;
|
||||
} catch (err: any) {
|
||||
@ -121,9 +125,9 @@ export const useFriendshipNetwork = (networkId: string | null) => {
|
||||
setPeople(people.filter(person => person._id !== personId));
|
||||
|
||||
// Remove all relationships involving this person
|
||||
setRelationships(relationships.filter(
|
||||
rel => rel.source !== personId && rel.target !== personId
|
||||
));
|
||||
setRelationships(
|
||||
relationships.filter(rel => rel.source !== personId && rel.target !== personId)
|
||||
);
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Failed to delete person');
|
||||
throw err;
|
||||
@ -162,12 +166,19 @@ export const useFriendshipNetwork = (networkId: string | null) => {
|
||||
if (!networkId) throw new Error('No network selected');
|
||||
|
||||
try {
|
||||
const updatedRelationship = await updateRelationship(networkId, relationshipId, relationshipData);
|
||||
const updatedRelationshipEdge: RelationshipEdge = { ...updatedRelationship, id: updatedRelationship._id };
|
||||
const updatedRelationship = await updateRelationship(
|
||||
networkId,
|
||||
relationshipId,
|
||||
relationshipData
|
||||
);
|
||||
const updatedRelationshipEdge: RelationshipEdge = {
|
||||
...updatedRelationship,
|
||||
id: updatedRelationship._id,
|
||||
};
|
||||
|
||||
setRelationships(relationships.map(rel =>
|
||||
rel._id === relationshipId ? updatedRelationshipEdge : rel
|
||||
));
|
||||
setRelationships(
|
||||
relationships.map(rel => (rel._id === relationshipId ? updatedRelationshipEdge : rel))
|
||||
);
|
||||
|
||||
return updatedRelationshipEdge;
|
||||
} catch (err: any) {
|
||||
@ -205,6 +216,6 @@ export const useFriendshipNetwork = (networkId: string | null) => {
|
||||
createRelationship,
|
||||
updateRelationship: updateRelationshipData,
|
||||
deleteRelationship,
|
||||
refreshNetwork
|
||||
refreshNetwork,
|
||||
};
|
||||
};
|
File diff suppressed because it is too large
Load Diff
1965
package-lock.json
generated
Normal file
1965
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,10 @@
|
||||
"scripts": {
|
||||
"start": "node dist/server.js",
|
||||
"dev": "nodemon --exec ts-node src/server.ts",
|
||||
"build": "tsc"
|
||||
"build": "tsc",
|
||||
"format": "prettier --write \"src/**/*.{ts,js,json}\"",
|
||||
"format:check": "prettier --check \"src/**/*.{ts,js,json}\"",
|
||||
"format:all": "npm run format && cd frontend && npm run format"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -38,6 +41,7 @@
|
||||
"@types/mongoose": "^5.11.97",
|
||||
"@types/node": "^22.14.1",
|
||||
"nodemon": "^3.1.9",
|
||||
"prettier": "^3.5.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
16
src/app.ts
16
src/app.ts
@ -6,20 +6,21 @@ import authRoutes from './routes/auth.routes';
|
||||
import networkRoutes from './routes/network.routes';
|
||||
import peopleRoutes from './routes/people.routes';
|
||||
import relationshipRoutes from './routes/relationship.routes';
|
||||
import path from "node:path";
|
||||
import path from 'node:path';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app: Application = express();
|
||||
|
||||
|
||||
// Middleware
|
||||
app.use(express.json());
|
||||
app.use(cookieParser());
|
||||
app.use(cors({
|
||||
app.use(
|
||||
cors({
|
||||
origin: process.env.CLIENT_URL || 'http://localhost:3000',
|
||||
credentials: true
|
||||
}));
|
||||
credentials: true,
|
||||
})
|
||||
);
|
||||
|
||||
// Routes
|
||||
app.use('/api/auth', authRoutes);
|
||||
@ -32,11 +33,10 @@ app.use('/api/networks', relationshipRoutes);
|
||||
res.send('Friendship Network API is running');
|
||||
});*/
|
||||
|
||||
|
||||
app.use(express.static(path.join(__dirname, '../frontend/dist/')));
|
||||
|
||||
app.use((req,res,next) => {
|
||||
app.use((req, res, next) => {
|
||||
res.sendFile(path.join(__dirname, '..', 'frontend/dist/index.html'));
|
||||
})
|
||||
});
|
||||
|
||||
export default app;
|
@ -38,9 +38,8 @@ export const register = async (req: Request, res: Response): Promise<void> => {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!process.env.ENABLE_REGISTRATION)
|
||||
{
|
||||
res.status(403).json({errors: ["Registration is disabled"]});
|
||||
if (!process.env.ENABLE_REGISTRATION) {
|
||||
res.status(403).json({ errors: ['Registration is disabled'] });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -15,10 +15,7 @@ export const getUserNetworks = async (req: UserRequest, res: Response): Promise<
|
||||
// 1. Belong to the current user, OR
|
||||
// 2. Are public networks (created by any user)
|
||||
const networks = await Network.find({
|
||||
$or: [
|
||||
{ owner: req.user._id },
|
||||
{ isPublic: true }
|
||||
]
|
||||
$or: [{ owner: req.user._id }, { isPublic: true }],
|
||||
}).populate('owner', 'username _id'); // Populate owner field with username
|
||||
|
||||
res.json({ success: true, data: networks });
|
||||
|
@ -165,10 +165,12 @@ export const removePerson = async (req: UserRequest, res: Response): Promise<voi
|
||||
// Remove the person
|
||||
await person.deleteOne(); // Changed from remove() to deleteOne()
|
||||
|
||||
res.json({ success: true, message: 'Person and associated relationships removed successfully' });
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Person and associated relationships removed successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Remove person error:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -9,7 +9,8 @@ const JWT_SECRET = process.env.JWT_SECRET || 'your_jwt_secret_key_change_this';
|
||||
export const auth = async (req: UserRequest, res: Response, next: NextFunction): Promise<void> => {
|
||||
try {
|
||||
// Get token from cookie or authorization header
|
||||
const token = req.cookies.token ||
|
||||
const token =
|
||||
req.cookies.token ||
|
||||
(req.headers.authorization && req.headers.authorization.startsWith('Bearer')
|
||||
? req.headers.authorization.split(' ')[1]
|
||||
: null);
|
||||
|
@ -2,7 +2,11 @@ import { Response, NextFunction } from 'express';
|
||||
import Network from '../models/network.model';
|
||||
import { UserRequest } from '../types/express';
|
||||
|
||||
export const checkNetworkAccess = async (req: UserRequest, res: Response, next: NextFunction): Promise<void> => {
|
||||
export const checkNetworkAccess = async (
|
||||
req: UserRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const networkId = req.params.networkId;
|
||||
|
||||
|
@ -33,4 +33,3 @@ const NetworkSchema = new Schema(
|
||||
);
|
||||
|
||||
export default mongoose.model<INetwork>('Network', NetworkSchema);
|
||||
|
||||
|
@ -40,9 +40,6 @@ const RelationshipSchema = new Schema(
|
||||
);
|
||||
|
||||
// Create compound index to ensure unique relationships in a network
|
||||
RelationshipSchema.index(
|
||||
{ source: 1, target: 1, network: 1 },
|
||||
{ unique: true }
|
||||
);
|
||||
RelationshipSchema.index({ source: 1, target: 1, network: 1 }, { unique: true });
|
||||
|
||||
export default mongoose.model<IRelationship>('Relationship', RelationshipSchema);
|
@ -33,7 +33,7 @@ const UserSchema = new Schema(
|
||||
);
|
||||
|
||||
// Hash password before saving
|
||||
UserSchema.pre('save', async function(next) {
|
||||
UserSchema.pre('save', async function (next) {
|
||||
const user = this;
|
||||
if (!user.isModified('password')) return next();
|
||||
|
||||
@ -47,7 +47,7 @@ UserSchema.pre('save', async function(next) {
|
||||
});
|
||||
|
||||
// Compare password method
|
||||
UserSchema.methods.comparePassword = async function(candidatePassword: string): Promise<boolean> {
|
||||
UserSchema.methods.comparePassword = async function (candidatePassword: string): Promise<boolean> {
|
||||
return bcrypt.compare(candidatePassword, this.password);
|
||||
};
|
||||
|
||||
|
@ -18,9 +18,7 @@ router.get('/', networkController.getUserNetworks);
|
||||
// @access Private
|
||||
router.post(
|
||||
'/',
|
||||
[
|
||||
check('name', 'Network name is required').not().isEmpty(),
|
||||
],
|
||||
[check('name', 'Network name is required').not().isEmpty()],
|
||||
networkController.createNetwork
|
||||
);
|
||||
|
||||
@ -34,9 +32,7 @@ router.get('/:id', networkController.getNetwork);
|
||||
// @access Private
|
||||
router.put(
|
||||
'/:id',
|
||||
[
|
||||
check('name', 'Network name is required if provided').optional().not().isEmpty(),
|
||||
],
|
||||
[check('name', 'Network name is required if provided').optional().not().isEmpty()],
|
||||
networkController.updateNetwork
|
||||
);
|
||||
|
||||
|
@ -22,7 +22,13 @@ router.post(
|
||||
[
|
||||
check('source', 'Source person ID is required').not().isEmpty().isMongoId(),
|
||||
check('target', 'Target person ID is required').not().isEmpty().isMongoId(),
|
||||
check('type', 'Relationship type is required').isIn(['freund', 'partner', 'familie', 'arbeitskolleg', 'custom']),
|
||||
check('type', 'Relationship type is required').isIn([
|
||||
'freund',
|
||||
'partner',
|
||||
'familie',
|
||||
'arbeitskolleg',
|
||||
'custom',
|
||||
]),
|
||||
check('customType', 'Custom type is required when type is custom')
|
||||
.if(check('type').equals('custom'))
|
||||
.not()
|
||||
|
Loading…
x
Reference in New Issue
Block a user