mirror of
https://github.com/philipredstone/relnet.git
synced 2025-06-17 05:01:24 +02:00
Compare commits
5 Commits
bbb3645d99
...
c31b5c5b14
Author | SHA1 | Date | |
---|---|---|---|
c31b5c5b14 | |||
0333d37aae | |||
3da29516ec | |||
00e7294f41 | |||
b054d55018 |
@ -1,4 +1,6 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { RELATIONSHIP_TYPES } from '../types/RelationShipTypes';
|
||||||
|
import { Relationship } from '../interfaces/IRelationship';
|
||||||
|
|
||||||
const protocol = window.location.protocol;
|
const protocol = window.location.protocol;
|
||||||
const hostname = window.location.hostname;
|
const hostname = window.location.hostname;
|
||||||
@ -6,27 +8,15 @@ const port = window.location.port;
|
|||||||
|
|
||||||
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
|
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
|
||||||
|
|
||||||
// Types
|
|
||||||
export interface Relationship {
|
|
||||||
_id: string;
|
|
||||||
source: string;
|
|
||||||
target: string;
|
|
||||||
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
|
|
||||||
customType?: string;
|
|
||||||
network: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateRelationshipData {
|
export interface CreateRelationshipData {
|
||||||
source: string;
|
source: string;
|
||||||
target: string;
|
target: string;
|
||||||
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
|
type: RELATIONSHIP_TYPES;
|
||||||
customType?: string;
|
customType?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateRelationshipData {
|
export interface UpdateRelationshipData {
|
||||||
type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
|
type?: RELATIONSHIP_TYPES;
|
||||||
customType?: string;
|
customType?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,9 +35,9 @@ interface CanvasGraphProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Physics constants
|
// Physics constants
|
||||||
const NODE_RADIUS = 30; // Node radius in pixels
|
const NODE_RADIUS = 45; // Node radius in pixels
|
||||||
const MIN_DISTANCE = 100; // Minimum distance between any two nodes
|
const MIN_DISTANCE = 110; // Minimum distance between any two nodes
|
||||||
const MAX_DISTANCE = 300; // Maximum distance between connected nodes
|
const MAX_DISTANCE = 500; // Maximum distance between connected nodes
|
||||||
const REPULSION_STRENGTH = 500; // How strongly nodes repel each other when too close
|
const REPULSION_STRENGTH = 500; // How strongly nodes repel each other when too close
|
||||||
const ATTRACTION_STRENGTH = 0.1; // Default attraction between connected nodes
|
const ATTRACTION_STRENGTH = 0.1; // Default attraction between connected nodes
|
||||||
const CONSTRAINT_STRENGTH = 0.2; // Strength of distance constraints
|
const CONSTRAINT_STRENGTH = 0.2; // Strength of distance constraints
|
||||||
@ -573,9 +573,9 @@ const CanvasGraph: React.FC<CanvasGraphProps> = ({ data, width, height, zoomLeve
|
|||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// Draw initials
|
// Draw initials
|
||||||
const initials = `${node.firstName.charAt(0)}${node.lastName.charAt(0)}`;
|
const initials = `${node.firstName} ${node.lastName.charAt(0)}.`;
|
||||||
ctx.fillStyle = 'white';
|
ctx.fillStyle = 'white';
|
||||||
ctx.font = 'bold 16px sans-serif';
|
ctx.font = 'bold 13px sans-serif';
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.fillText(initials, pos.x, pos.y);
|
ctx.fillText(initials, pos.x, pos.y);
|
||||||
|
@ -32,51 +32,24 @@ import {
|
|||||||
|
|
||||||
// Import custom UI components
|
// Import custom UI components
|
||||||
import {
|
import {
|
||||||
Button, Card, CardBody, ConfirmDialog, EmptyState, FormField, Modal, NetworkStats, Toast, ToastItem, Tooltip,
|
Button,
|
||||||
|
Card,
|
||||||
|
CardBody,
|
||||||
|
ConfirmDialog,
|
||||||
|
EmptyState,
|
||||||
|
FormField,
|
||||||
|
Modal,
|
||||||
|
NetworkStats,
|
||||||
|
Toast,
|
||||||
|
ToastItem,
|
||||||
|
Tooltip,
|
||||||
} from './FriendshipNetworkComponents';
|
} from './FriendshipNetworkComponents';
|
||||||
|
|
||||||
// Import visible canvas graph component
|
// Import visible canvas graph component
|
||||||
import CanvasGraph from './CanvasGraph';
|
import CanvasGraph from './CanvasGraph';
|
||||||
|
import { getRelationshipColor, RELATIONSHIP_TYPES, RELATIONSHIPS } from '../types/RelationShipTypes';
|
||||||
|
import { FormErrors, PersonNode } from '../interfaces/IPersonNode';
|
||||||
|
|
||||||
// Define types
|
|
||||||
type RelationshipType = 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
|
|
||||||
|
|
||||||
interface PersonNode {
|
|
||||||
_id: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
birthday?: Date | string | null;
|
|
||||||
notes?: string;
|
|
||||||
position?: {
|
|
||||||
x: number; y: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RelationshipEdge {
|
|
||||||
_id: string;
|
|
||||||
source: string;
|
|
||||||
target: string;
|
|
||||||
type: RelationshipType;
|
|
||||||
customType?: string;
|
|
||||||
notes?: string;
|
|
||||||
}
|
|
||||||
// Type for form errors
|
|
||||||
interface FormErrors {
|
|
||||||
[key: string]: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Graph appearance constants
|
|
||||||
const RELATIONSHIP_COLORS = {
|
|
||||||
freund: '#60A5FA', // Light blue
|
|
||||||
partner: '#F472B6', // Pink
|
|
||||||
familie: '#34D399', // Green
|
|
||||||
arbeitskolleg: '#FBBF24', // Yellow
|
|
||||||
custom: '#9CA3AF', // Gray
|
|
||||||
};
|
|
||||||
|
|
||||||
const RELATIONSHIP_LABELS = {
|
|
||||||
freund: 'Friend', partner: 'Partner', familie: 'Family', arbeitskolleg: 'Colleague', custom: 'Custom',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Main FriendshipNetwork component
|
// Main FriendshipNetwork component
|
||||||
const FriendshipNetwork: React.FC = () => {
|
const FriendshipNetwork: React.FC = () => {
|
||||||
@ -139,7 +112,7 @@ const FriendshipNetwork: React.FC = () => {
|
|||||||
const [editPerson, setEditPerson] = useState<PersonNode | null>(null);
|
const [editPerson, setEditPerson] = useState<PersonNode | null>(null);
|
||||||
|
|
||||||
const [newRelationship, setNewRelationship] = useState({
|
const [newRelationship, setNewRelationship] = useState({
|
||||||
source: '', target: '', type: 'freund' as RelationshipType, customType: '', notes: '', bidirectional: true,
|
source: '', target: '', type: 'friend' as RELATIONSHIP_TYPES, customType: '', notes: '', bidirectional: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Filter states
|
// Filter states
|
||||||
@ -402,7 +375,7 @@ const FriendshipNetwork: React.FC = () => {
|
|||||||
|
|
||||||
// Create edges
|
// Create edges
|
||||||
const graphEdges = relationships.map(rel => {
|
const graphEdges = relationships.map(rel => {
|
||||||
const color = RELATIONSHIP_COLORS[rel.type] || RELATIONSHIP_COLORS.custom;
|
const color = RELATIONSHIPS[rel.type as RELATIONSHIP_TYPES]?.color || RELATIONSHIPS.custom.color;
|
||||||
const width = rel.type === 'partner' ? 4 : rel.type === 'familie' ? 3 : 2;
|
const width = rel.type === 'partner' ? 4 : rel.type === 'familie' ? 3 : 2;
|
||||||
|
|
||||||
// Highlight edges connected to selected node
|
// Highlight edges connected to selected node
|
||||||
@ -542,7 +515,7 @@ const FriendshipNetwork: React.FC = () => {
|
|||||||
|
|
||||||
// Reset form and close modal
|
// Reset form and close modal
|
||||||
setNewRelationship({
|
setNewRelationship({
|
||||||
source: '', target: '', type: 'freund', customType: '', notes: '', bidirectional: true,
|
source: '', target: '', type: 'friend', customType: '', notes: '', bidirectional: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
setRelationshipModalOpen(false);
|
setRelationshipModalOpen(false);
|
||||||
@ -778,14 +751,14 @@ const FriendshipNetwork: React.FC = () => {
|
|||||||
<CardBody>
|
<CardBody>
|
||||||
<h3 className="font-medium mb-2 text-indigo-400">Legend</h3>
|
<h3 className="font-medium mb-2 text-indigo-400">Legend</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{Object.entries(RELATIONSHIP_COLORS).map(([type, color]) => (
|
{Object.entries(RELATIONSHIPS).map(([type, { label, color }]) => (
|
||||||
<div key={type} className="flex items-center text-sm">
|
<div key={type} className="flex items-center text-sm">
|
||||||
<div
|
<div
|
||||||
className="w-4 h-4 rounded-full mr-2"
|
className="w-4 h-4 rounded-full mr-2"
|
||||||
style={{ backgroundColor: color }}
|
style={{ backgroundColor: color }}
|
||||||
></div>
|
></div>
|
||||||
<span className="capitalize">
|
<span className="capitalize">
|
||||||
{RELATIONSHIP_LABELS[type as RelationshipType]}
|
{RELATIONSHIPS[type]?.label}
|
||||||
</span>
|
</span>
|
||||||
</div>))}
|
</div>))}
|
||||||
</div>
|
</div>
|
||||||
@ -919,17 +892,17 @@ const FriendshipNetwork: React.FC = () => {
|
|||||||
>
|
>
|
||||||
All Types
|
All Types
|
||||||
</button>
|
</button>
|
||||||
{Object.entries(RELATIONSHIP_COLORS).map(([type, color]) => (<button
|
{Object.entries(RELATIONSHIPS).map(([type, { label, color }]) => (<button
|
||||||
key={type}
|
key={type}
|
||||||
className={`px-3 py-1 text-xs rounded-full whitespace-nowrap flex items-center ${relationshipTypeFilter === type ? 'bg-indigo-600 text-white' : 'bg-slate-700 text-slate-300 hover:bg-slate-600'}`}
|
className={`px-3 py-1 text-xs rounded-full whitespace-nowrap flex items-center ${relationshipTypeFilter === type ? 'bg-indigo-600 text-white' : 'bg-slate-700 text-slate-300 hover:bg-slate-600'}`}
|
||||||
onClick={() => setRelationshipTypeFilter(type as RelationshipType)}
|
onClick={() => setRelationshipTypeFilter(type as RELATIONSHIP_TYPES)}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="w-2 h-2 rounded-full mr-1"
|
className="w-2 h-2 rounded-full mr-1"
|
||||||
style={{ backgroundColor: color }}
|
style={{ backgroundColor: color }}
|
||||||
></span>
|
></span>
|
||||||
<span className="capitalize">
|
<span className="capitalize">
|
||||||
{RELATIONSHIP_LABELS[type as RelationshipType]}
|
{RELATIONSHIPS[type as RELATIONSHIP_TYPES]?.label}
|
||||||
</span>
|
</span>
|
||||||
</button>))}
|
</button>))}
|
||||||
</div>
|
</div>
|
||||||
@ -974,10 +947,10 @@ const FriendshipNetwork: React.FC = () => {
|
|||||||
<div className="flex items-center text-xs text-slate-400 mt-1">
|
<div className="flex items-center text-xs text-slate-400 mt-1">
|
||||||
<span
|
<span
|
||||||
className="inline-block w-2 h-2 rounded-full mr-1"
|
className="inline-block w-2 h-2 rounded-full mr-1"
|
||||||
style={{ backgroundColor: RELATIONSHIP_COLORS[rel.type] }}
|
style={{ backgroundColor: RELATIONSHIPS[rel.type as RELATIONSHIP_TYPES]?.color }}
|
||||||
></span>
|
></span>
|
||||||
<span className="capitalize">
|
<span className="capitalize">
|
||||||
{rel.type === 'custom' ? rel.customType : RELATIONSHIP_LABELS[rel.type]}
|
{rel.type === 'custom' ? rel.customType : RELATIONSHIPS[rel.type as RELATIONSHIP_TYPES]?.label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1150,6 +1123,7 @@ const FriendshipNetwork: React.FC = () => {
|
|||||||
className={`w-full bg-slate-700 border ${personFormErrors.firstName ? 'border-red-500' : 'border-slate-600'}
|
className={`w-full bg-slate-700 border ${personFormErrors.firstName ? 'border-red-500' : 'border-slate-600'}
|
||||||
rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 text-white`}
|
rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 text-white`}
|
||||||
placeholder="Enter first name"
|
placeholder="Enter first name"
|
||||||
|
autoFocus={true}
|
||||||
value={newPerson.firstName}
|
value={newPerson.firstName}
|
||||||
onChange={e => setNewPerson({ ...newPerson, firstName: e.target.value })}
|
onChange={e => setNewPerson({ ...newPerson, firstName: e.target.value })}
|
||||||
/>
|
/>
|
||||||
@ -1236,6 +1210,7 @@ const FriendshipNetwork: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
id="source"
|
id="source"
|
||||||
|
autoFocus={true}
|
||||||
className={`w-full bg-slate-700 border ${relationshipFormErrors.source ? 'border-red-500' : 'border-slate-600'}
|
className={`w-full bg-slate-700 border ${relationshipFormErrors.source ? 'border-red-500' : 'border-slate-600'}
|
||||||
rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 text-white`}
|
rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-indigo-500 text-white`}
|
||||||
value={newRelationship.source}
|
value={newRelationship.source}
|
||||||
@ -1275,10 +1250,10 @@ const FriendshipNetwork: React.FC = () => {
|
|||||||
focus:outline-none focus:ring-2 focus:ring-indigo-500 text-white"
|
focus:outline-none focus:ring-2 focus:ring-indigo-500 text-white"
|
||||||
value={newRelationship.type}
|
value={newRelationship.type}
|
||||||
onChange={e => setNewRelationship({
|
onChange={e => setNewRelationship({
|
||||||
...newRelationship, type: e.target.value as RelationshipType,
|
...newRelationship, type: e.target.value as RELATIONSHIP_TYPES,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{Object.entries(RELATIONSHIP_LABELS).map(([value, label]) => (<option key={value} value={value}>
|
{Object.entries(RELATIONSHIPS).map(([value, { label }]) => (<option key={value} value={value}>
|
||||||
{label}
|
{label}
|
||||||
</option>))}
|
</option>))}
|
||||||
</select>
|
</select>
|
||||||
@ -1474,7 +1449,7 @@ const FriendshipNetwork: React.FC = () => {
|
|||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span
|
<span
|
||||||
className="inline-block w-2 h-2 rounded-full mr-2"
|
className="inline-block w-2 h-2 rounded-full mr-2"
|
||||||
style={{ backgroundColor: RELATIONSHIP_COLORS[rel.type] }}
|
style={{ backgroundColor: RELATIONSHIPS[rel.type as RELATIONSHIP_TYPES]?.color }}
|
||||||
></span>
|
></span>
|
||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
{isSource ? 'To: ' : 'From: '}
|
{isSource ? 'To: ' : 'From: '}
|
||||||
@ -1491,7 +1466,9 @@ const FriendshipNetwork: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{otherPerson.firstName} {otherPerson.lastName}
|
{otherPerson.firstName} {otherPerson.lastName}
|
||||||
</span>
|
</span>
|
||||||
{rel.type === 'custom' ? ` (${rel.customType})` : ` (${RELATIONSHIP_LABELS[rel.type]})`}
|
{rel.type === 'custom'
|
||||||
|
? ` (${rel.customType})`
|
||||||
|
: ` (${RELATIONSHIPS[rel.type as RELATIONSHIP_TYPES]?.label})`}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
@ -3,10 +3,11 @@ import { addPerson, getPeople, Person, removePerson, updatePerson } from '../api
|
|||||||
import {
|
import {
|
||||||
addRelationship,
|
addRelationship,
|
||||||
getRelationships,
|
getRelationships,
|
||||||
Relationship,
|
|
||||||
removeRelationship,
|
removeRelationship,
|
||||||
updateRelationship,
|
updateRelationship,
|
||||||
} from '../api/relationships';
|
} from '../api/relationships';
|
||||||
|
import { Relationship } from '../interfaces/IRelationship';
|
||||||
|
import { RELATIONSHIP_TYPES } from '../types/RelationShipTypes';
|
||||||
|
|
||||||
interface PersonNode extends Person {
|
interface PersonNode extends Person {
|
||||||
// Additional properties needed for the visualization
|
// Additional properties needed for the visualization
|
||||||
@ -314,7 +315,7 @@ export const useFriendshipNetwork = (
|
|||||||
const createRelationship = async (relationshipData: {
|
const createRelationship = async (relationshipData: {
|
||||||
source: string;
|
source: string;
|
||||||
target: string;
|
target: string;
|
||||||
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
|
type: RELATIONSHIP_TYPES;
|
||||||
customType?: string;
|
customType?: string;
|
||||||
}): Promise<RelationshipEdge> => {
|
}): Promise<RelationshipEdge> => {
|
||||||
if (!networkId) throw new Error('No network selected');
|
if (!networkId) throw new Error('No network selected');
|
||||||
@ -342,7 +343,7 @@ export const useFriendshipNetwork = (
|
|||||||
const updateRelationshipData = async (
|
const updateRelationshipData = async (
|
||||||
relationshipId: string,
|
relationshipId: string,
|
||||||
relationshipData: {
|
relationshipData: {
|
||||||
type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
|
type?: RELATIONSHIP_TYPES;
|
||||||
customType?: string;
|
customType?: string;
|
||||||
},
|
},
|
||||||
): Promise<RelationshipEdge> => {
|
): Promise<RelationshipEdge> => {
|
||||||
|
15
frontend/src/interfaces/IPersonNode.tsx
Normal file
15
frontend/src/interfaces/IPersonNode.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export interface PersonNode {
|
||||||
|
_id: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
birthday?: Date | string | null;
|
||||||
|
notes?: string;
|
||||||
|
position?: {
|
||||||
|
x: number; y: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type for form errors
|
||||||
|
export interface FormErrors {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
13
frontend/src/interfaces/IRelationship.ts
Normal file
13
frontend/src/interfaces/IRelationship.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Types
|
||||||
|
import { RELATIONSHIP_TYPES } from '../types/RelationShipTypes';
|
||||||
|
|
||||||
|
export interface Relationship {
|
||||||
|
_id: string;
|
||||||
|
source: string;
|
||||||
|
target: string;
|
||||||
|
type: RELATIONSHIP_TYPES;
|
||||||
|
customType?: string;
|
||||||
|
network: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
15
frontend/src/types/RelationShipTypes.ts
Normal file
15
frontend/src/types/RelationShipTypes.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export type RELATIONSHIP_TYPES = 'acquaintance' | 'friend' | 'partner' | 'family' | 'secondDegree' | 'colleague' | 'teacher' | 'exPartner' | 'custom';
|
||||||
|
|
||||||
|
export const RELATIONSHIPS: Record<RELATIONSHIP_TYPES, { label: string; color: string }> = {
|
||||||
|
acquaintance: { label: 'Bekannter', color: '#60A5FA' }, // Light blue
|
||||||
|
friend: { label: 'Freund', color: '#60A5FA' }, // Light blue
|
||||||
|
partner: { label: 'Partner', color: '#F472B6' }, // Pink
|
||||||
|
family: { label: 'Familie', color: '#34D399' }, // Green
|
||||||
|
secondDegree: { label: 'Verwandter', color: '#34D399' }, // Green
|
||||||
|
colleague: { label: 'Kollege/Klassenkamerad', color: '#FBBF24' }, // Yellow
|
||||||
|
teacher: { label: 'Lehrer', color: '#FBBF24' }, // Yellow
|
||||||
|
exPartner: { label: 'Ex-Partner', color: '#ce8c13' }, // Orange
|
||||||
|
custom: { label: 'Benutzerdefiniert', color: '#9CA3AF' }, // Gray
|
||||||
|
};
|
||||||
|
export const getRelationshipLabel = (type: RELATIONSHIP_TYPES): string => RELATIONSHIPS[type].label;
|
||||||
|
export const getRelationshipColor = (type: RELATIONSHIP_TYPES): string => RELATIONSHIPS[type].color;
|
@ -217,12 +217,12 @@ const createSampleDemoNetwork = async (userId: mongoose.Types.ObjectId | string)
|
|||||||
|
|
||||||
// Create relationships between people
|
// Create relationships between people
|
||||||
const relationships = [
|
const relationships = [
|
||||||
{ source: 'JohnSmith', target: 'EmmaJohnson', type: 'freund' },
|
{ source: 'JohnSmith', target: 'EmmaJohnson', type: 'friend' },
|
||||||
{ source: 'EmmaJohnson', target: 'MichaelWilliams', type: 'familie' },
|
{ source: 'EmmaJohnson', target: 'MichaelWilliams', type: 'family' },
|
||||||
{ source: 'MichaelWilliams', target: 'SarahBrown', type: 'arbeitskolleg' },
|
{ source: 'MichaelWilliams', target: 'SarahBrown', type: 'colleague' },
|
||||||
{ source: 'SarahBrown', target: 'DavidJones', type: 'freund' },
|
{ source: 'SarahBrown', target: 'DavidJones', type: 'friend' },
|
||||||
{ source: 'DavidJones', target: 'LisaGarcia', type: 'partner' },
|
{ source: 'DavidJones', target: 'LisaGarcia', type: 'partner' },
|
||||||
{ source: 'JohnSmith', target: 'DavidJones', type: 'arbeitskolleg' },
|
{ source: 'JohnSmith', target: 'DavidJones', type: 'colleague' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Create each relationship
|
// Create each relationship
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import mongoose, { Document, Schema } from 'mongoose';
|
import mongoose, { Document, Schema } from 'mongoose';
|
||||||
|
|
||||||
|
|
||||||
|
export const RELATIONSHIP_TYPES = [
|
||||||
|
'acquaintance', 'friend', 'partner', 'family', 'secondDegree', 'colleague', 'teacher', 'exPartner', 'custom',
|
||||||
|
];
|
||||||
|
|
||||||
export interface IRelationship extends Document {
|
export interface IRelationship extends Document {
|
||||||
_id: string;
|
_id: string;
|
||||||
source: mongoose.Types.ObjectId;
|
source: mongoose.Types.ObjectId;
|
||||||
@ -24,7 +29,7 @@ const RelationshipSchema = new Schema(
|
|||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: [true, 'Relationship type is required'],
|
required: [true, 'Relationship type is required'],
|
||||||
enum: ['freund', 'partner', 'familie', 'arbeitskolleg', 'custom'],
|
enum: RELATIONSHIP_TYPES,
|
||||||
},
|
},
|
||||||
customType: {
|
customType: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -36,7 +41,7 @@ const RelationshipSchema = new Schema(
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create compound index to ensure unique relationships in a network
|
// Create compound index to ensure unique relationships in a network
|
||||||
|
@ -3,6 +3,8 @@ import { check } from 'express-validator';
|
|||||||
import * as relationshipController from '../controllers/relationship.controller';
|
import * as relationshipController from '../controllers/relationship.controller';
|
||||||
import { auth } from '../middleware/auth.middleware';
|
import { auth } from '../middleware/auth.middleware';
|
||||||
import { checkNetworkAccess } from '../middleware/network-access.middleware';
|
import { checkNetworkAccess } from '../middleware/network-access.middleware';
|
||||||
|
import { RELATIONSHIP_TYPES } from '../models/relationship.model';
|
||||||
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@ -22,13 +24,7 @@ router.post(
|
|||||||
[
|
[
|
||||||
check('source', 'Source person ID is required').not().isEmpty().isMongoId(),
|
check('source', 'Source person ID is required').not().isEmpty().isMongoId(),
|
||||||
check('target', 'Target person ID is required').not().isEmpty().isMongoId(),
|
check('target', 'Target person ID is required').not().isEmpty().isMongoId(),
|
||||||
check('type', 'Relationship type is required').isIn([
|
check('type', 'Relationship type is required').isIn(RELATIONSHIP_TYPES),
|
||||||
'freund',
|
|
||||||
'partner',
|
|
||||||
'familie',
|
|
||||||
'arbeitskolleg',
|
|
||||||
'custom',
|
|
||||||
]),
|
|
||||||
check('customType', 'Custom type is required when type is custom')
|
check('customType', 'Custom type is required when type is custom')
|
||||||
.if(check('type').equals('custom'))
|
.if(check('type').equals('custom'))
|
||||||
.not()
|
.not()
|
||||||
@ -45,7 +41,7 @@ router.put(
|
|||||||
[
|
[
|
||||||
check('type', 'Relationship type must be valid if provided')
|
check('type', 'Relationship type must be valid if provided')
|
||||||
.optional()
|
.optional()
|
||||||
.isIn(['freund', 'partner', 'familie', 'arbeitskolleg', 'custom']),
|
.isIn(RELATIONSHIP_TYPES),
|
||||||
check('customType', 'Custom type is required when type is custom')
|
check('customType', 'Custom type is required when type is custom')
|
||||||
.if(check('type').equals('custom'))
|
.if(check('type').equals('custom'))
|
||||||
.not()
|
.not()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user