add prettifier

This commit is contained in:
philipredstone 2025-04-15 14:46:06 +02:00
parent c078610c4d
commit eceacf2117
43 changed files with 10946 additions and 6173 deletions

19
.prettierignore Normal file
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -1,67 +1,67 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { AuthProvider, useAuth } from './context/AuthContext';
import { NetworkProvider } from './context/NetworkContext';
import Login from './components/auth/Login';
import Register from './components/auth/Register';
import NetworkList from './components/networks/NetworkList';
import FriendshipNetwork from './components/FriendshipNetwork'; // Your existing component
import Header from './components/layout/Header';
// Protected route component
const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { user, loading } = useAuth();
if (loading) {
return <div className="flex justify-center items-center h-screen">Loading...</div>;
}
if (!user) {
return <Navigate to="/login" />;
}
return <>{children}</>;
};
const App: React.FC = () => {
return (
<AuthProvider>
<NetworkProvider>
<Router>
<div className="flex flex-col min-h-screen">
<Header />
<main className="flex-grow">
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route
path="/networks"
element={
<ProtectedRoute>
<NetworkList />
</ProtectedRoute>
}
/>
<Route
path="/networks/:id"
element={
<ProtectedRoute>
<FriendshipNetwork />
</ProtectedRoute>
}
/>
<Route path="/" element={<Navigate to="/networks" />} />
<Route path="*" element={<Navigate to="/networks" />} />
</Routes>
</main>
</div>
</Router>
</NetworkProvider>
</AuthProvider>
);
};
export default App;
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { AuthProvider, useAuth } from './context/AuthContext';
import { NetworkProvider } from './context/NetworkContext';
import Login from './components/auth/Login';
import Register from './components/auth/Register';
import NetworkList from './components/networks/NetworkList';
import FriendshipNetwork from './components/FriendshipNetwork'; // Your existing component
import Header from './components/layout/Header';
// Protected route component
const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { user, loading } = useAuth();
if (loading) {
return <div className="flex justify-center items-center h-screen">Loading...</div>;
}
if (!user) {
return <Navigate to="/login" />;
}
return <>{children}</>;
};
const App: React.FC = () => {
return (
<AuthProvider>
<NetworkProvider>
<Router>
<div className="flex flex-col min-h-screen">
<Header />
<main className="flex-grow">
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route
path="/networks"
element={
<ProtectedRoute>
<NetworkList />
</ProtectedRoute>
}
/>
<Route
path="/networks/:id"
element={
<ProtectedRoute>
<FriendshipNetwork />
</ProtectedRoute>
}
/>
<Route path="/" element={<Navigate to="/networks" />} />
<Route path="*" element={<Navigate to="/networks" />} />
</Routes>
</main>
</div>
</Router>
</NetworkProvider>
</AuthProvider>
);
};
export default App;

View File

@ -1,60 +1,60 @@
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Configure axios
axios.defaults.withCredentials = true;
// Types
export interface RegisterData {
username: string;
email: string;
password: string;
}
export interface LoginData {
email: string;
password: string;
}
export interface User {
id: string;
username: string;
email: string;
}
export interface AuthResponse {
success: boolean;
user: User;
}
// Register user
export const register = async (data: RegisterData): Promise<User> => {
const response = await axios.post<AuthResponse>(`${API_URL}/auth/register`, data);
return response.data.user;
};
// Login user
export const login = async (data: LoginData): Promise<User> => {
const response = await axios.post<AuthResponse>(`${API_URL}/auth/login`, data);
return response.data.user;
};
// Logout user
export const logout = async (): Promise<void> => {
await axios.post(`${API_URL}/auth/logout`);
};
// Get current user
export const getCurrentUser = async (): Promise<User | null> => {
try {
const response = await axios.get<AuthResponse>(`${API_URL}/auth/me`);
return response.data.user;
} catch (error) {
return null;
}
};
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Configure axios
axios.defaults.withCredentials = true;
// Types
export interface RegisterData {
username: string;
email: string;
password: string;
}
export interface LoginData {
email: string;
password: string;
}
export interface User {
id: string;
username: string;
email: string;
}
export interface AuthResponse {
success: boolean;
user: User;
}
// Register user
export const register = async (data: RegisterData): Promise<User> => {
const response = await axios.post<AuthResponse>(`${API_URL}/auth/register`, data);
return response.data.user;
};
// Login user
export const login = async (data: LoginData): Promise<User> => {
const response = await axios.post<AuthResponse>(`${API_URL}/auth/login`, data);
return response.data.user;
};
// Logout user
export const logout = async (): Promise<void> => {
await axios.post(`${API_URL}/auth/logout`);
};
// Get current user
export const getCurrentUser = async (): Promise<User | null> => {
try {
const response = await axios.get<AuthResponse>(`${API_URL}/auth/me`);
return response.data.user;
} catch (error) {
return null;
}
};

View File

@ -1,64 +1,72 @@
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Types
export interface NetworkOwner {
_id: string;
username: string;
}
export interface Network {
_id: string;
name: string;
description?: string;
owner: string | NetworkOwner; // Can be either string ID or populated object
isPublic: boolean;
createdAt: string;
updatedAt: string;
}
export interface CreateNetworkData {
name: string;
description?: string;
isPublic?: boolean;
}
export interface UpdateNetworkData {
name?: string;
description?: string;
isPublic?: boolean;
}
// Get all networks for current user
export const getUserNetworks = async (): Promise<Network[]> => {
const response = await axios.get<{ success: boolean; data: Network[] }>(`${API_URL}/networks`);
return response.data.data;
};
// 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);
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}`);
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);
return response.data.data;
};
// Delete a network
export const deleteNetwork = async (id: string): Promise<void> => {
await axios.delete(`${API_URL}/networks/${id}`);
};
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Types
export interface NetworkOwner {
_id: string;
username: string;
}
export interface Network {
_id: string;
name: string;
description?: string;
owner: string | NetworkOwner; // Can be either string ID or populated object
isPublic: boolean;
createdAt: string;
updatedAt: string;
}
export interface CreateNetworkData {
name: string;
description?: string;
isPublic?: boolean;
}
export interface UpdateNetworkData {
name?: string;
description?: string;
isPublic?: boolean;
}
// Get all networks for current user
export const getUserNetworks = async (): Promise<Network[]> => {
const response = await axios.get<{ success: boolean; data: Network[] }>(`${API_URL}/networks`);
return response.data.data;
};
// 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
);
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}`
);
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
);
return response.data.data;
};
// Delete a network
export const deleteNetwork = async (id: string): Promise<void> => {
await axios.delete(`${API_URL}/networks/${id}`);
};

View File

@ -1,77 +1,77 @@
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Types
export interface Person {
_id: string;
firstName: string;
lastName: string;
birthday?: string;
network: string;
position: {
x: number;
y: number;
};
createdAt: string;
updatedAt: string;
}
export interface CreatePersonData {
firstName: string;
lastName: string;
birthday?: string;
position?: {
x: number;
y: number;
};
}
export interface UpdatePersonData {
firstName?: string;
lastName?: string;
birthday?: string | null;
position?: {
x: number;
y: number;
};
}
// Get all people in a network
export const getPeople = async (networkId: string): Promise<Person[]> => {
const response = await axios.get<{ success: boolean; data: Person[] }>(
`${API_URL}/networks/${networkId}/people`
);
return response.data.data;
};
// Add a person to the network
export const addPerson = async (networkId: string, data: CreatePersonData): Promise<Person> => {
const response = await axios.post<{ success: boolean; data: Person }>(
`${API_URL}/networks/${networkId}/people`,
data
);
return response.data.data;
};
// Update a person
export const updatePerson = async (
networkId: string,
personId: string,
data: UpdatePersonData
): Promise<Person> => {
const response = await axios.put<{ success: boolean; data: Person }>(
`${API_URL}/networks/${networkId}/people/${personId}`,
data
);
return response.data.data;
};
// Remove a person from the network
export const removePerson = async (networkId: string, personId: string): Promise<void> => {
await axios.delete(`${API_URL}/networks/${networkId}/people/${personId}`);
};
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Types
export interface Person {
_id: string;
firstName: string;
lastName: string;
birthday?: string;
network: string;
position: {
x: number;
y: number;
};
createdAt: string;
updatedAt: string;
}
export interface CreatePersonData {
firstName: string;
lastName: string;
birthday?: string;
position?: {
x: number;
y: number;
};
}
export interface UpdatePersonData {
firstName?: string;
lastName?: string;
birthday?: string | null;
position?: {
x: number;
y: number;
};
}
// Get all people in a network
export const getPeople = async (networkId: string): Promise<Person[]> => {
const response = await axios.get<{ success: boolean; data: Person[] }>(
`${API_URL}/networks/${networkId}/people`
);
return response.data.data;
};
// Add a person to the network
export const addPerson = async (networkId: string, data: CreatePersonData): Promise<Person> => {
const response = await axios.post<{ success: boolean; data: Person }>(
`${API_URL}/networks/${networkId}/people`,
data
);
return response.data.data;
};
// Update a person
export const updatePerson = async (
networkId: string,
personId: string,
data: UpdatePersonData
): Promise<Person> => {
const response = await axios.put<{ success: boolean; data: Person }>(
`${API_URL}/networks/${networkId}/people/${personId}`,
data
);
return response.data.data;
};
// Remove a person from the network
export const removePerson = async (networkId: string, personId: string): Promise<void> => {
await axios.delete(`${API_URL}/networks/${networkId}/people/${personId}`);
};

View File

@ -1,69 +1,72 @@
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
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 {
source: string;
target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}
export interface UpdateRelationshipData {
type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}
// Get all relationships in a network
export const getRelationships = async (networkId: string): Promise<Relationship[]> => {
const response = await axios.get<{ success: boolean; data: Relationship[] }>(
`${API_URL}/networks/${networkId}/relationships`
);
return response.data.data;
};
// Add a relationship to the network
export const addRelationship = async (
networkId: string,
data: CreateRelationshipData
): Promise<Relationship> => {
const response = await axios.post<{ success: boolean; data: Relationship }>(
`${API_URL}/networks/${networkId}/relationships`,
data
);
return response.data.data;
};
// Update a relationship
export const updateRelationship = async (
networkId: string,
relationshipId: string,
data: UpdateRelationshipData
): Promise<Relationship> => {
const response = await axios.put<{ success: boolean; data: Relationship }>(
`${API_URL}/networks/${networkId}/relationships/${relationshipId}`,
data
);
return response.data.data;
};
// Remove a relationship
export const removeRelationship = async (networkId: string, relationshipId: string): Promise<void> => {
await axios.delete(`${API_URL}/networks/${networkId}/relationships/${relationshipId}`);
};
import axios from 'axios';
const protocol = window.location.protocol;
const hostname = window.location.hostname;
const port = window.location.port;
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 {
source: string;
target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}
export interface UpdateRelationshipData {
type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}
// Get all relationships in a network
export const getRelationships = async (networkId: string): Promise<Relationship[]> => {
const response = await axios.get<{ success: boolean; data: Relationship[] }>(
`${API_URL}/networks/${networkId}/relationships`
);
return response.data.data;
};
// Add a relationship to the network
export const addRelationship = async (
networkId: string,
data: CreateRelationshipData
): Promise<Relationship> => {
const response = await axios.post<{ success: boolean; data: Relationship }>(
`${API_URL}/networks/${networkId}/relationships`,
data
);
return response.data.data;
};
// Update a relationship
export const updateRelationship = async (
networkId: string,
relationshipId: string,
data: UpdateRelationshipData
): Promise<Relationship> => {
const response = await axios.put<{ success: boolean; data: Relationship }>(
`${API_URL}/networks/${networkId}/relationships/${relationshipId}`,
data
);
return response.data.data;
};
// Remove a relationship
export const removeRelationship = async (
networkId: string,
relationshipId: string
): Promise<void> => {
await axios.delete(`${API_URL}/networks/${networkId}/relationships/${relationshipId}`);
};

File diff suppressed because it is too large Load Diff

View File

@ -1,89 +1,89 @@
import React, { useState } from 'react';
import { useAuth } from '../../context/AuthContext';
import { useNavigate } from 'react-router-dom';
const Login: React.FC = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const { login } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setLoading(true);
try {
await login({ email, password });
navigate('/networks');
} catch (err: any) {
setError(err.response?.data?.message || 'Login failed. Please check your credentials.');
} finally {
setLoading(false);
}
};
return (
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="w-full max-w-md p-6 bg-white rounded-lg shadow-md">
<h2 className="text-2xl font-bold mb-6 text-center">Login</h2>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
Email
</label>
<input
id="email"
type="email"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="mb-6">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
Password
</label>
<input
id="password"
type="password"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<div className="flex items-center justify-between">
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
disabled={loading}
>
{loading ? 'Logging in...' : 'Login'}
</button>
<a
href="/register"
className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800"
>
Register
</a>
</div>
</form>
</div>
</div>
);
};
export default Login;
import React, { useState } from 'react';
import { useAuth } from '../../context/AuthContext';
import { useNavigate } from 'react-router-dom';
const Login: React.FC = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const { login } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setLoading(true);
try {
await login({ email, password });
navigate('/networks');
} catch (err: any) {
setError(err.response?.data?.message || 'Login failed. Please check your credentials.');
} finally {
setLoading(false);
}
};
return (
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="w-full max-w-md p-6 bg-white rounded-lg shadow-md">
<h2 className="text-2xl font-bold mb-6 text-center">Login</h2>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
Email
</label>
<input
id="email"
type="email"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={email}
onChange={e => setEmail(e.target.value)}
required
/>
</div>
<div className="mb-6">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
Password
</label>
<input
id="password"
type="password"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={password}
onChange={e => setPassword(e.target.value)}
required
/>
</div>
<div className="flex items-center justify-between">
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
disabled={loading}
>
{loading ? 'Logging in...' : 'Login'}
</button>
<a
href="/register"
className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800"
>
Register
</a>
</div>
</form>
</div>
</div>
);
};
export default Login;

View File

@ -1,131 +1,131 @@
import React, { useState } from 'react';
import { useAuth } from '../../context/AuthContext';
import { useNavigate } from 'react-router-dom';
const Register: React.FC = () => {
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const { register } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
// Basic validation
if (password !== confirmPassword) {
setError('Passwords do not match');
return;
}
if (password.length < 6) {
setError('Password must be at least 6 characters');
return;
}
setLoading(true);
try {
await register({ username, email, password });
navigate('/networks');
} catch (err: any) {
setError(err.response?.data?.message || 'Registration failed. Please try again.');
} finally {
setLoading(false);
}
};
return (
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="w-full max-w-md p-6 bg-white rounded-lg shadow-md">
<h2 className="text-2xl font-bold mb-6 text-center">Register</h2>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="username">
Username
</label>
<input
id="username"
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
Email
</label>
<input
id="email"
type="email"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
Password
</label>
<input
id="password"
type="password"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<div className="mb-6">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="confirmPassword">
Confirm Password
</label>
<input
id="confirmPassword"
type="password"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
/>
</div>
<div className="flex items-center justify-between">
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
disabled={loading}
>
{loading ? 'Registering...' : 'Register'}
</button>
<a
href="/login"
className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800"
>
Login
</a>
</div>
</form>
</div>
</div>
);
};
export default Register;
import React, { useState } from 'react';
import { useAuth } from '../../context/AuthContext';
import { useNavigate } from 'react-router-dom';
const Register: React.FC = () => {
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const { register } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
// Basic validation
if (password !== confirmPassword) {
setError('Passwords do not match');
return;
}
if (password.length < 6) {
setError('Password must be at least 6 characters');
return;
}
setLoading(true);
try {
await register({ username, email, password });
navigate('/networks');
} catch (err: any) {
setError(err.response?.data?.message || 'Registration failed. Please try again.');
} finally {
setLoading(false);
}
};
return (
<div className="flex items-center justify-center min-h-screen bg-gray-100">
<div className="w-full max-w-md p-6 bg-white rounded-lg shadow-md">
<h2 className="text-2xl font-bold mb-6 text-center">Register</h2>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="username">
Username
</label>
<input
id="username"
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={username}
onChange={e => setUsername(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
Email
</label>
<input
id="email"
type="email"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={email}
onChange={e => setEmail(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
Password
</label>
<input
id="password"
type="password"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={password}
onChange={e => setPassword(e.target.value)}
required
/>
</div>
<div className="mb-6">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="confirmPassword">
Confirm Password
</label>
<input
id="confirmPassword"
type="password"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={confirmPassword}
onChange={e => setConfirmPassword(e.target.value)}
required
/>
</div>
<div className="flex items-center justify-between">
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
disabled={loading}
>
{loading ? 'Registering...' : 'Register'}
</button>
<a
href="/login"
className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800"
>
Login
</a>
</div>
</form>
</div>
</div>
);
};
export default Register;

View File

@ -1,58 +1,58 @@
import React from 'react';
import {Link, useNavigate} from 'react-router-dom';
import {useAuth} from '../../context/AuthContext';
const Header: React.FC = () => {
const {user, logout} = useAuth();
const navigate = useNavigate();
const handleLogout = async () => {
try {
await logout();
navigate('/login');
} catch (error) {
console.error('Logout failed:', error);
}
};
return (
<header className="bg-blue-600 text-white shadow-md">
<div className="container mx-auto py-4 px-6 flex justify-between items-center">
<Link to="/" className="text-xl font-bold">
Friendship Network
</Link>
<nav>
{user ? (
<div className="flex items-center space-x-4">
<span>Hello, {user.username}</span>
<Link to="/networks" className="hover:underline">
My Networks
</Link>
<button
onClick={handleLogout}
className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-3 rounded"
>
Logout
</button>
</div>
) : (
<div className="space-x-4">
<Link to="/login" className="hover:underline">
Login
</Link>
<Link
to="/register"
className="bg-white text-blue-600 hover:bg-gray-100 font-bold py-1 px-3 rounded"
>
Register
</Link>
</div>
)}
</nav>
</div>
</header>
);
};
export default Header;
import React from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/AuthContext';
const Header: React.FC = () => {
const { user, logout } = useAuth();
const navigate = useNavigate();
const handleLogout = async () => {
try {
await logout();
navigate('/login');
} catch (error) {
console.error('Logout failed:', error);
}
};
return (
<header className="bg-blue-600 text-white shadow-md">
<div className="container mx-auto py-4 px-6 flex justify-between items-center">
<Link to="/" className="text-xl font-bold">
Friendship Network
</Link>
<nav>
{user ? (
<div className="flex items-center space-x-4">
<span>Hello, {user.username}</span>
<Link to="/networks" className="hover:underline">
My Networks
</Link>
<button
onClick={handleLogout}
className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-3 rounded"
>
Logout
</button>
</div>
) : (
<div className="space-x-4">
<Link to="/login" className="hover:underline">
Login
</Link>
<Link
to="/register"
className="bg-white text-blue-600 hover:bg-gray-100 font-bold py-1 px-3 rounded"
>
Register
</Link>
</div>
)}
</nav>
</div>
</header>
);
};
export default Header;

View File

@ -1,274 +1,274 @@
import React, { useState } from 'react';
import { useNetworks } from '../../context/NetworkContext';
import { useAuth } from '../../context/AuthContext';
import { useNavigate } from 'react-router-dom';
const NetworkList: React.FC = () => {
const { networks, loading, error, createNetwork, deleteNetwork } = useNetworks();
const { user } = useAuth();
const [showCreateForm, setShowCreateForm] = useState(false);
const [newNetworkName, setNewNetworkName] = useState('');
const [newNetworkDescription, setNewNetworkDescription] = useState('');
const [isPublic, setIsPublic] = useState(false);
const [formError, setFormError] = useState<string | null>(null);
const [createLoading, setCreateLoading] = useState(false);
const navigate = useNavigate();
const handleCreateNetwork = async (e: React.FormEvent) => {
e.preventDefault();
setFormError(null);
if (!newNetworkName.trim()) {
setFormError('Network name is required');
return;
}
setCreateLoading(true);
try {
const network = await createNetwork({
name: newNetworkName.trim(),
description: newNetworkDescription.trim() || undefined,
isPublic
});
// Reset form
setNewNetworkName('');
setNewNetworkDescription('');
setIsPublic(false);
setShowCreateForm(false);
// Navigate to the new network
navigate(`/networks/${network._id}`);
} catch (err: any) {
setFormError(err.response?.data?.message || 'Failed to create network');
} finally {
setCreateLoading(false);
}
};
const handleDeleteNetwork = async (id: string) => {
if (window.confirm('Are you sure you want to delete this network? This action cannot be undone.')) {
try {
await deleteNetwork(id);
} catch (err: any) {
alert(err.response?.data?.message || 'Failed to delete network');
}
}
};
if (loading) {
return <div className="flex justify-center p-8">Loading networks...</div>;
}
return (
<div className="container mx-auto p-6">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold">My Networks</h1>
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={() => setShowCreateForm(!showCreateForm)}
>
{showCreateForm ? 'Cancel' : 'Create New Network'}
</button>
</div>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
{/* Create Network Form */}
{showCreateForm && (
<div className="bg-gray-100 p-4 rounded-lg mb-6">
<h2 className="text-xl font-semibold mb-4">Create New Network</h2>
{formError && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{formError}
</div>
)}
<form onSubmit={handleCreateNetwork}>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="name">
Network Name *
</label>
<input
id="name"
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={newNetworkName}
onChange={(e) => setNewNetworkName(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="description">
Description (Optional)
</label>
<textarea
id="description"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={newNetworkDescription}
onChange={(e) => setNewNetworkDescription(e.target.value)}
rows={3}
/>
</div>
<div className="mb-4">
<label className="flex items-center">
<input
type="checkbox"
className="mr-2"
checked={isPublic}
onChange={(e) => setIsPublic(e.target.checked)}
/>
<span className="text-gray-700 text-sm font-bold">Make this network public</span>
</label>
</div>
<button
type="submit"
className="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded"
disabled={createLoading}
>
{createLoading ? 'Creating...' : 'Create Network'}
</button>
</form>
</div>
)}
{/* Networks List */}
{networks.length === 0 ? (
<div className="bg-white p-8 rounded-lg text-center">
<p className="text-gray-600 mb-4">You don't have any networks yet.</p>
{!showCreateForm && (
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={() => setShowCreateForm(true)}
>
Create Your First Network
</button>
)}
</div>
) : (
<>
{/* My Networks Section */}
<div className="mb-8">
<h2 className="text-xl font-semibold mb-4">My Networks</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{networks
.filter(network => {
if (!user) return false;
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">
<div className="p-4">
<h2 className="text-xl font-bold mb-2">{network.name}</h2>
{network.description && (
<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'}`}>
{network.isPublic ? 'Public' : 'Private'}
</span>
<span className="text-xs text-gray-500 ml-2">
Created: {new Date(network.createdAt).toLocaleDateString()}
</span>
</div>
<div className="flex space-x-2">
<button
className="flex-1 bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded"
onClick={() => navigate(`/networks/${network._id}`)}
>
View
</button>
<button
className="flex-1 bg-red-500 hover:bg-red-700 text-white py-2 px-4 rounded"
onClick={() => handleDeleteNetwork(network._id)}
>
Delete
</button>
</div>
</div>
</div>
))}
</div>
{networks.filter(network => {
if (!user) return false;
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>
)}
</div>
{/* Public Networks Section */}
{networks.some(network => {
if (!user) return false;
const ownerId = typeof network.owner === 'string'
? network.owner
: network.owner._id;
return ownerId !== user.id;
}) && (
<div>
<h2 className="text-xl font-semibold mb-4">Public Networks From Others</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{networks
.filter(network => {
if (!user) return false;
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">
<div className="p-4">
<h2 className="text-xl font-bold mb-2">{network.name}</h2>
{network.description && (
<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 bg-green-100 text-green-800">
Public
</span>
<span className="text-xs text-gray-500 ml-2">
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}
</span>
</div>
<div className="flex space-x-2">
<button
className="w-full bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded"
onClick={() => navigate(`/networks/${network._id}`)}
>
View
</button>
</div>
</div>
</div>
))}
</div>
</div>
)}
</>
)}
</div>
);
};
export default NetworkList;
import React, { useState } from 'react';
import { useNetworks } from '../../context/NetworkContext';
import { useAuth } from '../../context/AuthContext';
import { useNavigate } from 'react-router-dom';
const NetworkList: React.FC = () => {
const { networks, loading, error, createNetwork, deleteNetwork } = useNetworks();
const { user } = useAuth();
const [showCreateForm, setShowCreateForm] = useState(false);
const [newNetworkName, setNewNetworkName] = useState('');
const [newNetworkDescription, setNewNetworkDescription] = useState('');
const [isPublic, setIsPublic] = useState(false);
const [formError, setFormError] = useState<string | null>(null);
const [createLoading, setCreateLoading] = useState(false);
const navigate = useNavigate();
const handleCreateNetwork = async (e: React.FormEvent) => {
e.preventDefault();
setFormError(null);
if (!newNetworkName.trim()) {
setFormError('Network name is required');
return;
}
setCreateLoading(true);
try {
const network = await createNetwork({
name: newNetworkName.trim(),
description: newNetworkDescription.trim() || undefined,
isPublic,
});
// Reset form
setNewNetworkName('');
setNewNetworkDescription('');
setIsPublic(false);
setShowCreateForm(false);
// Navigate to the new network
navigate(`/networks/${network._id}`);
} catch (err: any) {
setFormError(err.response?.data?.message || 'Failed to create network');
} finally {
setCreateLoading(false);
}
};
const handleDeleteNetwork = async (id: string) => {
if (
window.confirm('Are you sure you want to delete this network? This action cannot be undone.')
) {
try {
await deleteNetwork(id);
} catch (err: any) {
alert(err.response?.data?.message || 'Failed to delete network');
}
}
};
if (loading) {
return <div className="flex justify-center p-8">Loading networks...</div>;
}
return (
<div className="container mx-auto p-6">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold">My Networks</h1>
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={() => setShowCreateForm(!showCreateForm)}
>
{showCreateForm ? 'Cancel' : 'Create New Network'}
</button>
</div>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
{/* Create Network Form */}
{showCreateForm && (
<div className="bg-gray-100 p-4 rounded-lg mb-6">
<h2 className="text-xl font-semibold mb-4">Create New Network</h2>
{formError && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{formError}
</div>
)}
<form onSubmit={handleCreateNetwork}>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="name">
Network Name *
</label>
<input
id="name"
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={newNetworkName}
onChange={e => setNewNetworkName(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="description">
Description (Optional)
</label>
<textarea
id="description"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={newNetworkDescription}
onChange={e => setNewNetworkDescription(e.target.value)}
rows={3}
/>
</div>
<div className="mb-4">
<label className="flex items-center">
<input
type="checkbox"
className="mr-2"
checked={isPublic}
onChange={e => setIsPublic(e.target.checked)}
/>
<span className="text-gray-700 text-sm font-bold">Make this network public</span>
</label>
</div>
<button
type="submit"
className="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded"
disabled={createLoading}
>
{createLoading ? 'Creating...' : 'Create Network'}
</button>
</form>
</div>
)}
{/* Networks List */}
{networks.length === 0 ? (
<div className="bg-white p-8 rounded-lg text-center">
<p className="text-gray-600 mb-4">You don't have any networks yet.</p>
{!showCreateForm && (
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={() => setShowCreateForm(true)}
>
Create Your First Network
</button>
)}
</div>
) : (
<>
{/* My Networks Section */}
<div className="mb-8">
<h2 className="text-xl font-semibold mb-4">My Networks</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{networks
.filter(network => {
if (!user) return false;
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">
<div className="p-4">
<h2 className="text-xl font-bold mb-2">{network.name}</h2>
{network.description && (
<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'}`}
>
{network.isPublic ? 'Public' : 'Private'}
</span>
<span className="text-xs text-gray-500 ml-2">
Created: {new Date(network.createdAt).toLocaleDateString()}
</span>
</div>
<div className="flex space-x-2">
<button
className="flex-1 bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded"
onClick={() => navigate(`/networks/${network._id}`)}
>
View
</button>
<button
className="flex-1 bg-red-500 hover:bg-red-700 text-white py-2 px-4 rounded"
onClick={() => handleDeleteNetwork(network._id)}
>
Delete
</button>
</div>
</div>
</div>
))}
</div>
{networks.filter(network => {
if (!user) return false;
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>
)}
</div>
{/* Public Networks Section */}
{networks.some(network => {
if (!user) return false;
const ownerId = typeof network.owner === 'string' ? network.owner : network.owner._id;
return ownerId !== user.id;
}) && (
<div>
<h2 className="text-xl font-semibold mb-4">Public Networks From Others</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{networks
.filter(network => {
if (!user) return false;
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"
>
<div className="p-4">
<h2 className="text-xl font-bold mb-2">{network.name}</h2>
{network.description && (
<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 bg-green-100 text-green-800">
Public
</span>
<span className="text-xs text-gray-500 ml-2">
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}
</span>
</div>
<div className="flex space-x-2">
<button
className="w-full bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded"
onClick={() => navigate(`/networks/${network._id}`)}
>
View
</button>
</div>
</div>
</div>
))}
</div>
</div>
)}
</>
)}
</div>
);
};
export default NetworkList;

View File

@ -1,70 +1,78 @@
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';
interface AuthContextProps {
user: User | null;
loading: boolean;
login: (data: LoginData) => Promise<void>;
register: (data: RegisterData) => Promise<void>;
logout: () => Promise<void>;
}
const AuthContext = createContext<AuthContextProps>({} as AuthContextProps);
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadUser = async () => {
try {
const currentUser = await getCurrentUser();
setUser(currentUser);
} catch (error) {
console.error('Error loading user:', error);
} finally {
setLoading(false);
}
};
loadUser();
}, []);
const login = async (data: LoginData) => {
try {
const loggedInUser = await apiLogin(data);
setUser(loggedInUser);
} catch (error) {
console.error('Login error:', error);
throw error;
}
};
const register = async (data: RegisterData) => {
try {
const newUser = await apiRegister(data);
setUser(newUser);
} catch (error) {
console.error('Registration error:', error);
throw error;
}
};
const logout = async () => {
try {
await apiLogout();
setUser(null);
} catch (error) {
console.error('Logout error:', error);
throw error;
}
};
return (
<AuthContext.Provider value={{ user, loading, login, register, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
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';
interface AuthContextProps {
user: User | null;
loading: boolean;
login: (data: LoginData) => Promise<void>;
register: (data: RegisterData) => Promise<void>;
logout: () => Promise<void>;
}
const AuthContext = createContext<AuthContextProps>({} as AuthContextProps);
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadUser = async () => {
try {
const currentUser = await getCurrentUser();
setUser(currentUser);
} catch (error) {
console.error('Error loading user:', error);
} finally {
setLoading(false);
}
};
loadUser();
}, []);
const login = async (data: LoginData) => {
try {
const loggedInUser = await apiLogin(data);
setUser(loggedInUser);
} catch (error) {
console.error('Login error:', error);
throw error;
}
};
const register = async (data: RegisterData) => {
try {
const newUser = await apiRegister(data);
setUser(newUser);
} catch (error) {
console.error('Registration error:', error);
throw error;
}
};
const logout = async () => {
try {
await apiLogout();
setUser(null);
} catch (error) {
console.error('Logout error:', error);
throw error;
}
};
return (
<AuthContext.Provider value={{ user, loading, login, register, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);

View File

@ -1,110 +1,108 @@
import React, { createContext, useState, useEffect, useContext, ReactNode } from 'react';
import {
Network,
getUserNetworks,
createNetwork as apiCreateNetwork,
updateNetwork as apiUpdateNetwork,
deleteNetwork as apiDeleteNetwork,
CreateNetworkData,
UpdateNetworkData
} from '../api/network';
import { useAuth } from './AuthContext';
interface NetworkContextProps {
networks: Network[];
loading: boolean;
error: string | null;
createNetwork: (data: CreateNetworkData) => Promise<Network>;
updateNetwork: (id: string, data: UpdateNetworkData) => Promise<Network>;
deleteNetwork: (id: string) => Promise<void>;
refreshNetworks: () => Promise<void>;
}
const NetworkContext = createContext<NetworkContextProps>({} as NetworkContextProps);
export const NetworkProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [networks, setNetworks] = useState<Network[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const { user } = useAuth();
const loadNetworks = async () => {
if (!user) {
setNetworks([]);
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
const fetchedNetworks = await getUserNetworks();
setNetworks(fetchedNetworks);
} catch (err: any) {
setError(err.message || 'Failed to load networks');
console.error('Error loading networks:', err);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadNetworks();
}, [user]);
const createNetwork = async (data: CreateNetworkData): Promise<Network> => {
try {
const newNetwork = await apiCreateNetwork(data);
setNetworks([...networks, newNetwork]);
return newNetwork;
} catch (err: any) {
setError(err.message || 'Failed to create network');
throw err;
}
};
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
));
return updatedNetwork;
} catch (err: any) {
setError(err.message || 'Failed to update network');
throw err;
}
};
const deleteNetwork = async (id: string): Promise<void> => {
try {
await apiDeleteNetwork(id);
setNetworks(networks.filter(network => network._id !== id));
} catch (err: any) {
setError(err.message || 'Failed to delete network');
throw err;
}
};
const refreshNetworks = async (): Promise<void> => {
await loadNetworks();
};
return (
<NetworkContext.Provider
value={{
networks,
loading,
error,
createNetwork,
updateNetwork,
deleteNetwork,
refreshNetworks
}}
>
{children}
</NetworkContext.Provider>
);
};
export const useNetworks = () => useContext(NetworkContext);
import React, { createContext, useState, useEffect, useContext, ReactNode } from 'react';
import {
Network,
getUserNetworks,
createNetwork as apiCreateNetwork,
updateNetwork as apiUpdateNetwork,
deleteNetwork as apiDeleteNetwork,
CreateNetworkData,
UpdateNetworkData,
} from '../api/network';
import { useAuth } from './AuthContext';
interface NetworkContextProps {
networks: Network[];
loading: boolean;
error: string | null;
createNetwork: (data: CreateNetworkData) => Promise<Network>;
updateNetwork: (id: string, data: UpdateNetworkData) => Promise<Network>;
deleteNetwork: (id: string) => Promise<void>;
refreshNetworks: () => Promise<void>;
}
const NetworkContext = createContext<NetworkContextProps>({} as NetworkContextProps);
export const NetworkProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [networks, setNetworks] = useState<Network[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const { user } = useAuth();
const loadNetworks = async () => {
if (!user) {
setNetworks([]);
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
const fetchedNetworks = await getUserNetworks();
setNetworks(fetchedNetworks);
} catch (err: any) {
setError(err.message || 'Failed to load networks');
console.error('Error loading networks:', err);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadNetworks();
}, [user]);
const createNetwork = async (data: CreateNetworkData): Promise<Network> => {
try {
const newNetwork = await apiCreateNetwork(data);
setNetworks([...networks, newNetwork]);
return newNetwork;
} catch (err: any) {
setError(err.message || 'Failed to create network');
throw err;
}
};
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)));
return updatedNetwork;
} catch (err: any) {
setError(err.message || 'Failed to update network');
throw err;
}
};
const deleteNetwork = async (id: string): Promise<void> => {
try {
await apiDeleteNetwork(id);
setNetworks(networks.filter(network => network._id !== id));
} catch (err: any) {
setError(err.message || 'Failed to delete network');
throw err;
}
};
const refreshNetworks = async (): Promise<void> => {
await loadNetworks();
};
return (
<NetworkContext.Provider
value={{
networks,
loading,
error,
createNetwork,
updateNetwork,
deleteNetwork,
refreshNetworks,
}}
>
{children}
</NetworkContext.Provider>
);
};
export const useNetworks = () => useContext(NetworkContext);

View File

@ -1,210 +1,221 @@
import { useState, useEffect, useCallback } from 'react';
import { Person, getPeople, addPerson, updatePerson, removePerson } from '../api/people';
import { Relationship, getRelationships, addRelationship, updateRelationship, removeRelationship } from '../api/relationships';
interface PersonNode extends Person {
// Additional properties needed for the visualization
id: string; // Alias for _id to work with the visualization
}
interface RelationshipEdge extends Relationship {
// Additional properties needed for the visualization
id: string; // Alias for _id to work with the visualization
}
// Custom hook to manage friendship network data
export const useFriendshipNetwork = (networkId: string | null) => {
const [people, setPeople] = useState<PersonNode[]>([]);
const [relationships, setRelationships] = useState<RelationshipEdge[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Load network data
const loadNetworkData = useCallback(async () => {
if (!networkId) {
setPeople([]);
setRelationships([]);
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
// Fetch people and relationships in parallel
const [peopleData, relationshipsData] = await Promise.all([
getPeople(networkId),
getRelationships(networkId)
]);
// Transform to add the id property needed by the visualization
const peopleNodes: PersonNode[] = peopleData.map(person => ({
...person,
id: person._id
}));
const relationshipEdges: RelationshipEdge[] = relationshipsData.map(rel => ({
...rel,
id: rel._id
}));
setPeople(peopleNodes);
setRelationships(relationshipEdges);
} catch (err: any) {
setError(err.message || 'Failed to load network data');
console.error('Error loading network data:', err);
} finally {
setLoading(false);
}
}, [networkId]);
useEffect(() => {
loadNetworkData();
}, [loadNetworkData]);
// Add a new person
const createPerson = async (personData: {
firstName: string;
lastName: string;
birthday?: string;
position?: { x: number; y: number }
}): Promise<PersonNode> => {
if (!networkId) throw new Error('No network selected');
try {
const newPerson = await addPerson(networkId, personData);
const newPersonNode: PersonNode = { ...newPerson, id: newPerson._id };
setPeople([...people, newPersonNode]);
return newPersonNode;
} catch (err: any) {
setError(err.message || 'Failed to create person');
throw err;
}
};
// Update a person
const updatePersonData = async (
personId: string,
personData: {
firstName?: string;
lastName?: string;
birthday?: string | null;
position?: { x: number; y: number }
}
): Promise<PersonNode> => {
if (!networkId) throw new Error('No network selected');
try {
const updatedPerson = await updatePerson(networkId, personId, personData);
const updatedPersonNode: PersonNode = { ...updatedPerson, id: updatedPerson._id };
setPeople(people.map(person =>
person._id === personId ? updatedPersonNode : person
));
return updatedPersonNode;
} catch (err: any) {
setError(err.message || 'Failed to update person');
throw err;
}
};
// Remove a person
const deletePerson = async (personId: string): Promise<void> => {
if (!networkId) throw new Error('No network selected');
try {
await removePerson(networkId, personId);
// Remove the person
setPeople(people.filter(person => person._id !== personId));
// Remove all relationships involving this person
setRelationships(relationships.filter(
rel => rel.source !== personId && rel.target !== personId
));
} catch (err: any) {
setError(err.message || 'Failed to delete person');
throw err;
}
};
// Create a relationship
const createRelationship = async (relationshipData: {
source: string;
target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}): Promise<RelationshipEdge> => {
if (!networkId) throw new Error('No network selected');
try {
const newRelationship = await addRelationship(networkId, relationshipData);
const newRelationshipEdge: RelationshipEdge = { ...newRelationship, id: newRelationship._id };
setRelationships([...relationships, newRelationshipEdge]);
return newRelationshipEdge;
} catch (err: any) {
setError(err.message || 'Failed to create relationship');
throw err;
}
};
// Update a relationship
const updateRelationshipData = async (
relationshipId: string,
relationshipData: {
type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}
): Promise<RelationshipEdge> => {
if (!networkId) throw new Error('No network selected');
try {
const updatedRelationship = await updateRelationship(networkId, relationshipId, relationshipData);
const updatedRelationshipEdge: RelationshipEdge = { ...updatedRelationship, id: updatedRelationship._id };
setRelationships(relationships.map(rel =>
rel._id === relationshipId ? updatedRelationshipEdge : rel
));
return updatedRelationshipEdge;
} catch (err: any) {
setError(err.message || 'Failed to update relationship');
throw err;
}
};
// Remove a relationship
const deleteRelationship = async (relationshipId: string): Promise<void> => {
if (!networkId) throw new Error('No network selected');
try {
await removeRelationship(networkId, relationshipId);
setRelationships(relationships.filter(rel => rel._id !== relationshipId));
} catch (err: any) {
setError(err.message || 'Failed to delete relationship');
throw err;
}
};
// Refresh the network data
const refreshNetwork = async (): Promise<void> => {
await loadNetworkData();
};
return {
people,
relationships,
loading,
error,
createPerson,
updatePerson: updatePersonData,
deletePerson,
createRelationship,
updateRelationship: updateRelationshipData,
deleteRelationship,
refreshNetwork
};
};
import { useState, useEffect, useCallback } from 'react';
import { Person, getPeople, addPerson, updatePerson, removePerson } from '../api/people';
import {
Relationship,
getRelationships,
addRelationship,
updateRelationship,
removeRelationship,
} from '../api/relationships';
interface PersonNode extends Person {
// Additional properties needed for the visualization
id: string; // Alias for _id to work with the visualization
}
interface RelationshipEdge extends Relationship {
// Additional properties needed for the visualization
id: string; // Alias for _id to work with the visualization
}
// Custom hook to manage friendship network data
export const useFriendshipNetwork = (networkId: string | null) => {
const [people, setPeople] = useState<PersonNode[]>([]);
const [relationships, setRelationships] = useState<RelationshipEdge[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Load network data
const loadNetworkData = useCallback(async () => {
if (!networkId) {
setPeople([]);
setRelationships([]);
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
// Fetch people and relationships in parallel
const [peopleData, relationshipsData] = await Promise.all([
getPeople(networkId),
getRelationships(networkId),
]);
// Transform to add the id property needed by the visualization
const peopleNodes: PersonNode[] = peopleData.map(person => ({
...person,
id: person._id,
}));
const relationshipEdges: RelationshipEdge[] = relationshipsData.map(rel => ({
...rel,
id: rel._id,
}));
setPeople(peopleNodes);
setRelationships(relationshipEdges);
} catch (err: any) {
setError(err.message || 'Failed to load network data');
console.error('Error loading network data:', err);
} finally {
setLoading(false);
}
}, [networkId]);
useEffect(() => {
loadNetworkData();
}, [loadNetworkData]);
// Add a new person
const createPerson = async (personData: {
firstName: string;
lastName: string;
birthday?: string;
position?: { x: number; y: number };
}): Promise<PersonNode> => {
if (!networkId) throw new Error('No network selected');
try {
const newPerson = await addPerson(networkId, personData);
const newPersonNode: PersonNode = { ...newPerson, id: newPerson._id };
setPeople([...people, newPersonNode]);
return newPersonNode;
} catch (err: any) {
setError(err.message || 'Failed to create person');
throw err;
}
};
// Update a person
const updatePersonData = async (
personId: string,
personData: {
firstName?: string;
lastName?: string;
birthday?: string | null;
position?: { x: number; y: number };
}
): Promise<PersonNode> => {
if (!networkId) throw new Error('No network selected');
try {
const updatedPerson = await updatePerson(networkId, personId, personData);
const updatedPersonNode: PersonNode = { ...updatedPerson, id: updatedPerson._id };
setPeople(people.map(person => (person._id === personId ? updatedPersonNode : person)));
return updatedPersonNode;
} catch (err: any) {
setError(err.message || 'Failed to update person');
throw err;
}
};
// Remove a person
const deletePerson = async (personId: string): Promise<void> => {
if (!networkId) throw new Error('No network selected');
try {
await removePerson(networkId, personId);
// Remove the person
setPeople(people.filter(person => person._id !== personId));
// Remove all relationships involving this person
setRelationships(
relationships.filter(rel => rel.source !== personId && rel.target !== personId)
);
} catch (err: any) {
setError(err.message || 'Failed to delete person');
throw err;
}
};
// Create a relationship
const createRelationship = async (relationshipData: {
source: string;
target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}): Promise<RelationshipEdge> => {
if (!networkId) throw new Error('No network selected');
try {
const newRelationship = await addRelationship(networkId, relationshipData);
const newRelationshipEdge: RelationshipEdge = { ...newRelationship, id: newRelationship._id };
setRelationships([...relationships, newRelationshipEdge]);
return newRelationshipEdge;
} catch (err: any) {
setError(err.message || 'Failed to create relationship');
throw err;
}
};
// Update a relationship
const updateRelationshipData = async (
relationshipId: string,
relationshipData: {
type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
}
): Promise<RelationshipEdge> => {
if (!networkId) throw new Error('No network selected');
try {
const updatedRelationship = await updateRelationship(
networkId,
relationshipId,
relationshipData
);
const updatedRelationshipEdge: RelationshipEdge = {
...updatedRelationship,
id: updatedRelationship._id,
};
setRelationships(
relationships.map(rel => (rel._id === relationshipId ? updatedRelationshipEdge : rel))
);
return updatedRelationshipEdge;
} catch (err: any) {
setError(err.message || 'Failed to update relationship');
throw err;
}
};
// Remove a relationship
const deleteRelationship = async (relationshipId: string): Promise<void> => {
if (!networkId) throw new Error('No network selected');
try {
await removeRelationship(networkId, relationshipId);
setRelationships(relationships.filter(rel => rel._id !== relationshipId));
} catch (err: any) {
setError(err.message || 'Failed to delete relationship');
throw err;
}
};
// Refresh the network data
const refreshNetwork = async (): Promise<void> => {
await loadNetworkData();
};
return {
people,
relationships,
loading,
error,
createPerson,
updatePerson: updatePersonData,
deletePerson,
createRelationship,
updateRelationship: updateRelationshipData,
deleteRelationship,
refreshNetwork,
};
};

View File

@ -1,17 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// Create root and render the App component into the HTML element with ID 'root'
const rootElement = document.getElementById('root');
if (rootElement) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
} else {
console.error('Root element not found');
}
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// Create root and render the App component into the HTML element with ID 'root'
const rootElement = document.getElementById('root');
if (rootElement) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
} else {
console.error('Root element not found');
}

File diff suppressed because it is too large Load Diff

1965
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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"
}

View File

@ -1,42 +1,42 @@
import express, { Application } from 'express';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import dotenv from 'dotenv';
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";
dotenv.config();
const app: Application = express();
// Middleware
app.use(express.json());
app.use(cookieParser());
app.use(cors({
origin: process.env.CLIENT_URL || 'http://localhost:3000',
credentials: true
}));
// Routes
app.use('/api/auth', authRoutes);
app.use('/api/networks', networkRoutes);
app.use('/api/networks', peopleRoutes);
app.use('/api/networks', relationshipRoutes);
// Base route
/*app.get('/', (req, res) => {
res.send('Friendship Network API is running');
});*/
app.use(express.static(path.join(__dirname, '../frontend/dist/')));
app.use((req,res,next) => {
res.sendFile(path.join(__dirname, '..', 'frontend/dist/index.html'));
})
export default app;
import express, { Application } from 'express';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import dotenv from 'dotenv';
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';
dotenv.config();
const app: Application = express();
// Middleware
app.use(express.json());
app.use(cookieParser());
app.use(
cors({
origin: process.env.CLIENT_URL || 'http://localhost:3000',
credentials: true,
})
);
// Routes
app.use('/api/auth', authRoutes);
app.use('/api/networks', networkRoutes);
app.use('/api/networks', peopleRoutes);
app.use('/api/networks', relationshipRoutes);
// Base route
/*app.get('/', (req, res) => {
res.send('Friendship Network API is running');
});*/
app.use(express.static(path.join(__dirname, '../frontend/dist/')));
app.use((req, res, next) => {
res.sendFile(path.join(__dirname, '..', 'frontend/dist/index.html'));
});
export default app;

View File

@ -1,18 +1,18 @@
import mongoose from 'mongoose';
import dotenv from 'dotenv';
dotenv.config();
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/friendship-network';
const connectDB = async (): Promise<void> => {
try {
await mongoose.connect(MONGODB_URI);
console.log('MongoDB connected successfully');
} catch (error) {
console.error('MongoDB connection error:', error);
process.exit(1);
}
};
export default connectDB;
import mongoose from 'mongoose';
import dotenv from 'dotenv';
dotenv.config();
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/friendship-network';
const connectDB = async (): Promise<void> => {
try {
await mongoose.connect(MONGODB_URI);
console.log('MongoDB connected successfully');
} catch (error) {
console.error('MongoDB connection error:', error);
process.exit(1);
}
};
export default connectDB;

View File

@ -1,166 +1,165 @@
import { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import User, { IUser } from '../models/user.model';
import { UserRequest } from '../types/express';
import { validationResult } from 'express-validator';
// JWT secret from environment variables
const JWT_SECRET = process.env.JWT_SECRET || 'your_jwt_secret_key_change_this';
// Token expiration (1 day)
const TOKEN_EXPIRY = '1d';
// Generate JWT token
const generateToken = (user: IUser): string => {
return jwt.sign({ id: user._id }, JWT_SECRET, {
expiresIn: TOKEN_EXPIRY,
});
};
// Set cookie with JWT token
const setTokenCookie = (res: Response, token: string): void => {
// Cookie options
const options = {
expires: new Date(Date.now() + 24 * 60 * 60 * 1000), // 1 day
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
};
res.cookie('token', token, options);
};
// Register a new user
export const register = async (req: Request, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
if(!process.env.ENABLE_REGISTRATION)
{
res.status(403).json({errors: ["Registration is disabled"]});
return;
}
const { email, password, username } = req.body;
// Check if user already exists
let user = await User.findOne({ email });
if (user) {
res.status(400).json({ message: 'User already exists' });
return;
}
// Create new user
user = new User({
email,
password,
username,
});
// Save user to database
await user.save();
// Generate JWT token
const token = generateToken(user);
// Set token cookie
setTokenCookie(res, token);
// Send response
res.status(201).json({
success: true,
user: {
id: user._id,
email: user.email,
username: user.username,
},
});
} catch (error) {
console.error('Registration error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Login user
export const login = async (req: Request, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
const { email, password } = req.body;
// Check if user exists
const user = await User.findOne({ email });
if (!user) {
res.status(400).json({ message: 'Invalid credentials' });
return;
}
// Check if password is correct
const isMatch = await user.comparePassword(password);
if (!isMatch) {
res.status(400).json({ message: 'Invalid credentials' });
return;
}
// Generate JWT token
const token = generateToken(user);
// Set token cookie
setTokenCookie(res, token);
// Send response
res.json({
success: true,
user: {
id: user._id,
email: user.email,
username: user.username,
},
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Logout user
export const logout = (req: Request, res: Response): void => {
res.cookie('token', 'none', {
expires: new Date(Date.now() + 10 * 1000), // 10 seconds
httpOnly: true,
});
res.json({ success: true, message: 'Logged out successfully' });
};
// Get current user
export const getCurrentUser = async (req: UserRequest, res: Response): Promise<void> => {
try {
const user = req.user;
if (!user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
res.json({
success: true,
user: {
id: user._id,
email: user.email,
username: user.username,
},
});
} catch (error) {
console.error('Get current user error:', error);
res.status(500).json({ message: 'Server error' });
}
};
import { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import User, { IUser } from '../models/user.model';
import { UserRequest } from '../types/express';
import { validationResult } from 'express-validator';
// JWT secret from environment variables
const JWT_SECRET = process.env.JWT_SECRET || 'your_jwt_secret_key_change_this';
// Token expiration (1 day)
const TOKEN_EXPIRY = '1d';
// Generate JWT token
const generateToken = (user: IUser): string => {
return jwt.sign({ id: user._id }, JWT_SECRET, {
expiresIn: TOKEN_EXPIRY,
});
};
// Set cookie with JWT token
const setTokenCookie = (res: Response, token: string): void => {
// Cookie options
const options = {
expires: new Date(Date.now() + 24 * 60 * 60 * 1000), // 1 day
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
};
res.cookie('token', token, options);
};
// Register a new user
export const register = async (req: Request, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
if (!process.env.ENABLE_REGISTRATION) {
res.status(403).json({ errors: ['Registration is disabled'] });
return;
}
const { email, password, username } = req.body;
// Check if user already exists
let user = await User.findOne({ email });
if (user) {
res.status(400).json({ message: 'User already exists' });
return;
}
// Create new user
user = new User({
email,
password,
username,
});
// Save user to database
await user.save();
// Generate JWT token
const token = generateToken(user);
// Set token cookie
setTokenCookie(res, token);
// Send response
res.status(201).json({
success: true,
user: {
id: user._id,
email: user.email,
username: user.username,
},
});
} catch (error) {
console.error('Registration error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Login user
export const login = async (req: Request, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
const { email, password } = req.body;
// Check if user exists
const user = await User.findOne({ email });
if (!user) {
res.status(400).json({ message: 'Invalid credentials' });
return;
}
// Check if password is correct
const isMatch = await user.comparePassword(password);
if (!isMatch) {
res.status(400).json({ message: 'Invalid credentials' });
return;
}
// Generate JWT token
const token = generateToken(user);
// Set token cookie
setTokenCookie(res, token);
// Send response
res.json({
success: true,
user: {
id: user._id,
email: user.email,
username: user.username,
},
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Logout user
export const logout = (req: Request, res: Response): void => {
res.cookie('token', 'none', {
expires: new Date(Date.now() + 10 * 1000), // 10 seconds
httpOnly: true,
});
res.json({ success: true, message: 'Logged out successfully' });
};
// Get current user
export const getCurrentUser = async (req: UserRequest, res: Response): Promise<void> => {
try {
const user = req.user;
if (!user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
res.json({
success: true,
user: {
id: user._id,
email: user.email,
username: user.username,
},
});
} catch (error) {
console.error('Get current user error:', error);
res.status(500).json({ message: 'Server error' });
}
};

View File

@ -1,169 +1,166 @@
import { Response } from 'express';
import Network from '../models/network.model';
import { UserRequest } from '../types/express';
import { validationResult } from 'express-validator';
// Get all networks for current user and all public networks
export const getUserNetworks = async (req: UserRequest, res: Response): Promise<void> => {
try {
if (!req.user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Find networks that either:
// 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 }
]
}).populate('owner', 'username _id'); // Populate owner field with username
res.json({ success: true, data: networks });
} catch (error) {
console.error('Get networks error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Create a new network
export const createNetwork = async (req: UserRequest, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
if (!req.user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const { name, description, isPublic } = req.body;
const network = new Network({
name,
description,
owner: req.user._id,
isPublic: isPublic || false,
});
await network.save();
res.status(201).json({ success: true, data: network });
} catch (error) {
console.error('Create network error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Get a specific network
export const getNetwork = async (req: UserRequest, res: Response): Promise<void> => {
try {
const networkId = req.params.id;
if (!req.user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const network = await Network.findById(networkId).populate('owner', 'username _id');
if (!network) {
res.status(404).json({ message: 'Network not found' });
return;
}
// Check if user is owner or network is public
if (network.owner._id.toString() !== req.user._id.toString() && !network.isPublic) {
res.status(403).json({ message: 'You do not have permission to access this network' });
return;
}
res.json({ success: true, data: network });
} catch (error) {
console.error('Get network error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Update a network
export const updateNetwork = async (req: UserRequest, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
const networkId = req.params.id;
if (!req.user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const network = await Network.findById(networkId);
if (!network) {
res.status(404).json({ message: 'Network not found' });
return;
}
// Check if user is owner
if (network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'You do not have permission to update this network' });
return;
}
const { name, description, isPublic } = req.body;
network.name = name || network.name;
network.description = description !== undefined ? description : network.description;
network.isPublic = isPublic !== undefined ? isPublic : network.isPublic;
await network.save();
res.json({ success: true, data: network });
} catch (error) {
console.error('Update network error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Delete a network
export const deleteNetwork = async (req: UserRequest, res: Response): Promise<void> => {
try {
const networkId = req.params.id;
if (!req.user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const network = await Network.findById(networkId);
if (!network) {
res.status(404).json({ message: 'Network not found' });
return;
}
// Check if user is owner
if (network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'You do not have permission to delete this network' });
return;
}
await network.deleteOne(); // Changed from remove() to deleteOne()
res.json({ success: true, message: 'Network deleted successfully' });
} catch (error) {
console.error('Delete network error:', error);
res.status(500).json({ message: 'Server error' });
}
};
import { Response } from 'express';
import Network from '../models/network.model';
import { UserRequest } from '../types/express';
import { validationResult } from 'express-validator';
// Get all networks for current user and all public networks
export const getUserNetworks = async (req: UserRequest, res: Response): Promise<void> => {
try {
if (!req.user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Find networks that either:
// 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 }],
}).populate('owner', 'username _id'); // Populate owner field with username
res.json({ success: true, data: networks });
} catch (error) {
console.error('Get networks error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Create a new network
export const createNetwork = async (req: UserRequest, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
if (!req.user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const { name, description, isPublic } = req.body;
const network = new Network({
name,
description,
owner: req.user._id,
isPublic: isPublic || false,
});
await network.save();
res.status(201).json({ success: true, data: network });
} catch (error) {
console.error('Create network error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Get a specific network
export const getNetwork = async (req: UserRequest, res: Response): Promise<void> => {
try {
const networkId = req.params.id;
if (!req.user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const network = await Network.findById(networkId).populate('owner', 'username _id');
if (!network) {
res.status(404).json({ message: 'Network not found' });
return;
}
// Check if user is owner or network is public
if (network.owner._id.toString() !== req.user._id.toString() && !network.isPublic) {
res.status(403).json({ message: 'You do not have permission to access this network' });
return;
}
res.json({ success: true, data: network });
} catch (error) {
console.error('Get network error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Update a network
export const updateNetwork = async (req: UserRequest, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
const networkId = req.params.id;
if (!req.user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const network = await Network.findById(networkId);
if (!network) {
res.status(404).json({ message: 'Network not found' });
return;
}
// Check if user is owner
if (network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'You do not have permission to update this network' });
return;
}
const { name, description, isPublic } = req.body;
network.name = name || network.name;
network.description = description !== undefined ? description : network.description;
network.isPublic = isPublic !== undefined ? isPublic : network.isPublic;
await network.save();
res.json({ success: true, data: network });
} catch (error) {
console.error('Update network error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Delete a network
export const deleteNetwork = async (req: UserRequest, res: Response): Promise<void> => {
try {
const networkId = req.params.id;
if (!req.user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const network = await Network.findById(networkId);
if (!network) {
res.status(404).json({ message: 'Network not found' });
return;
}
// Check if user is owner
if (network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'You do not have permission to delete this network' });
return;
}
await network.deleteOne(); // Changed from remove() to deleteOne()
res.json({ success: true, message: 'Network deleted successfully' });
} catch (error) {
console.error('Delete network error:', error);
res.status(500).json({ message: 'Server error' });
}
};

View File

@ -1,174 +1,176 @@
import { Response } from 'express';
import Person from '../models/person.model';
import Relationship from '../models/relationship.model';
import { UserRequest } from '../types/express';
import { validationResult } from 'express-validator';
import mongoose from 'mongoose';
// Get all people in a network
export const getPeople = async (req: UserRequest, res: Response): Promise<void> => {
try {
const networkId = req.params.networkId;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const people = await Person.find({ network: networkId });
res.json({ success: true, data: people });
} catch (error) {
console.error('Get people error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Add a person to the network
export const addPerson = async (req: UserRequest, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
const networkId = req.params.networkId;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can add people)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can add people' });
return;
}
const { firstName, lastName, birthday, position } = req.body;
// Check if person already exists in this network
const existingPerson = await Person.findOne({
firstName,
lastName,
network: networkId,
});
if (existingPerson) {
res.status(400).json({ message: 'This person already exists in the network' });
return;
}
const person = new Person({
firstName,
lastName,
birthday: birthday || undefined,
network: networkId,
position: position || { x: 100 + Math.random() * 500, y: 100 + Math.random() * 400 },
});
await person.save();
res.status(201).json({ success: true, data: person });
} catch (error) {
console.error('Add person error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Update a person
export const updatePerson = async (req: UserRequest, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
const networkId = req.params.networkId;
const personId = req.params.id;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can update people)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can update people' });
return;
}
const person = await Person.findOne({
_id: personId,
network: networkId,
});
if (!person) {
res.status(404).json({ message: 'Person not found' });
return;
}
const { firstName, lastName, birthday, position } = req.body;
// Update person
if (firstName) person.firstName = firstName;
if (lastName) person.lastName = lastName;
if (birthday !== undefined) person.birthday = birthday || undefined;
if (position) person.position = position;
await person.save();
res.json({ success: true, data: person });
} catch (error) {
console.error('Update person error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Remove a person from the network
export const removePerson = async (req: UserRequest, res: Response): Promise<void> => {
try {
const networkId = req.params.networkId;
const personId = req.params.id;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can remove people)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can remove people' });
return;
}
const person = await Person.findOne({
_id: personId,
network: networkId,
});
if (!person) {
res.status(404).json({ message: 'Person not found' });
return;
}
// Remove all relationships involving this person
await Relationship.deleteMany({
network: networkId,
$or: [{ source: personId }, { target: personId }],
});
// Remove the person
await person.deleteOne(); // Changed from remove() to deleteOne()
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' });
}
};
import { Response } from 'express';
import Person from '../models/person.model';
import Relationship from '../models/relationship.model';
import { UserRequest } from '../types/express';
import { validationResult } from 'express-validator';
import mongoose from 'mongoose';
// Get all people in a network
export const getPeople = async (req: UserRequest, res: Response): Promise<void> => {
try {
const networkId = req.params.networkId;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const people = await Person.find({ network: networkId });
res.json({ success: true, data: people });
} catch (error) {
console.error('Get people error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Add a person to the network
export const addPerson = async (req: UserRequest, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
const networkId = req.params.networkId;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can add people)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can add people' });
return;
}
const { firstName, lastName, birthday, position } = req.body;
// Check if person already exists in this network
const existingPerson = await Person.findOne({
firstName,
lastName,
network: networkId,
});
if (existingPerson) {
res.status(400).json({ message: 'This person already exists in the network' });
return;
}
const person = new Person({
firstName,
lastName,
birthday: birthday || undefined,
network: networkId,
position: position || { x: 100 + Math.random() * 500, y: 100 + Math.random() * 400 },
});
await person.save();
res.status(201).json({ success: true, data: person });
} catch (error) {
console.error('Add person error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Update a person
export const updatePerson = async (req: UserRequest, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
const networkId = req.params.networkId;
const personId = req.params.id;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can update people)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can update people' });
return;
}
const person = await Person.findOne({
_id: personId,
network: networkId,
});
if (!person) {
res.status(404).json({ message: 'Person not found' });
return;
}
const { firstName, lastName, birthday, position } = req.body;
// Update person
if (firstName) person.firstName = firstName;
if (lastName) person.lastName = lastName;
if (birthday !== undefined) person.birthday = birthday || undefined;
if (position) person.position = position;
await person.save();
res.json({ success: true, data: person });
} catch (error) {
console.error('Update person error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Remove a person from the network
export const removePerson = async (req: UserRequest, res: Response): Promise<void> => {
try {
const networkId = req.params.networkId;
const personId = req.params.id;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can remove people)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can remove people' });
return;
}
const person = await Person.findOne({
_id: personId,
network: networkId,
});
if (!person) {
res.status(404).json({ message: 'Person not found' });
return;
}
// Remove all relationships involving this person
await Relationship.deleteMany({
network: networkId,
$or: [{ source: personId }, { target: personId }],
});
// Remove the person
await person.deleteOne(); // Changed from remove() to deleteOne()
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' });
}
};

View File

@ -1,180 +1,180 @@
import { Response } from 'express';
import Relationship from '../models/relationship.model';
import Person from '../models/person.model';
import { UserRequest } from '../types/express';
import { validationResult } from 'express-validator';
// Get all relationships in a network
export const getRelationships = async (req: UserRequest, res: Response): Promise<void> => {
try {
const networkId = req.params.networkId;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const relationships = await Relationship.find({ network: networkId });
res.json({ success: true, data: relationships });
} catch (error) {
console.error('Get relationships error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Add a relationship to the network
export const addRelationship = async (req: UserRequest, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
const networkId = req.params.networkId;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can add relationships)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can add relationships' });
return;
}
const { source, target, type, customType } = req.body;
// Check if source and target exist and belong to the network
const sourcePerson = await Person.findOne({
_id: source,
network: networkId,
});
const targetPerson = await Person.findOne({
_id: target,
network: networkId,
});
if (!sourcePerson || !targetPerson) {
res.status(400).json({ message: 'Source or target person not found in this network' });
return;
}
// Check if relationship already exists
const existingRelationship = await Relationship.findOne({
$or: [
{ source, target, network: networkId },
{ source: target, target: source, network: networkId },
],
});
if (existingRelationship) {
res.status(400).json({ message: 'A relationship already exists between these people' });
return;
}
const relationship = new Relationship({
source,
target,
type,
customType: type === 'custom' ? customType : undefined,
network: networkId,
});
await relationship.save();
res.status(201).json({ success: true, data: relationship });
} catch (error) {
console.error('Add relationship error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Update a relationship
export const updateRelationship = async (req: UserRequest, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
const networkId = req.params.networkId;
const relationshipId = req.params.id;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can update relationships)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can update relationships' });
return;
}
const relationship = await Relationship.findOne({
_id: relationshipId,
network: networkId,
});
if (!relationship) {
res.status(404).json({ message: 'Relationship not found' });
return;
}
const { type, customType } = req.body;
// Update relationship
if (type) relationship.type = type;
if (type === 'custom' && customType) relationship.customType = customType;
await relationship.save();
res.json({ success: true, data: relationship });
} catch (error) {
console.error('Update relationship error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Remove a relationship
export const removeRelationship = async (req: UserRequest, res: Response): Promise<void> => {
try {
const networkId = req.params.networkId;
const relationshipId = req.params.id;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can remove relationships)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can remove relationships' });
return;
}
const relationship = await Relationship.findOne({
_id: relationshipId,
network: networkId,
});
if (!relationship) {
res.status(404).json({ message: 'Relationship not found' });
return;
}
await relationship.deleteOne(); // Changed from remove() to deleteOne()
res.json({ success: true, message: 'Relationship removed successfully' });
} catch (error) {
console.error('Remove relationship error:', error);
res.status(500).json({ message: 'Server error' });
}
};
import { Response } from 'express';
import Relationship from '../models/relationship.model';
import Person from '../models/person.model';
import { UserRequest } from '../types/express';
import { validationResult } from 'express-validator';
// Get all relationships in a network
export const getRelationships = async (req: UserRequest, res: Response): Promise<void> => {
try {
const networkId = req.params.networkId;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const relationships = await Relationship.find({ network: networkId });
res.json({ success: true, data: relationships });
} catch (error) {
console.error('Get relationships error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Add a relationship to the network
export const addRelationship = async (req: UserRequest, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
const networkId = req.params.networkId;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can add relationships)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can add relationships' });
return;
}
const { source, target, type, customType } = req.body;
// Check if source and target exist and belong to the network
const sourcePerson = await Person.findOne({
_id: source,
network: networkId,
});
const targetPerson = await Person.findOne({
_id: target,
network: networkId,
});
if (!sourcePerson || !targetPerson) {
res.status(400).json({ message: 'Source or target person not found in this network' });
return;
}
// Check if relationship already exists
const existingRelationship = await Relationship.findOne({
$or: [
{ source, target, network: networkId },
{ source: target, target: source, network: networkId },
],
});
if (existingRelationship) {
res.status(400).json({ message: 'A relationship already exists between these people' });
return;
}
const relationship = new Relationship({
source,
target,
type,
customType: type === 'custom' ? customType : undefined,
network: networkId,
});
await relationship.save();
res.status(201).json({ success: true, data: relationship });
} catch (error) {
console.error('Add relationship error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Update a relationship
export const updateRelationship = async (req: UserRequest, res: Response): Promise<void> => {
try {
// Validate request
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() });
return;
}
const networkId = req.params.networkId;
const relationshipId = req.params.id;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can update relationships)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can update relationships' });
return;
}
const relationship = await Relationship.findOne({
_id: relationshipId,
network: networkId,
});
if (!relationship) {
res.status(404).json({ message: 'Relationship not found' });
return;
}
const { type, customType } = req.body;
// Update relationship
if (type) relationship.type = type;
if (type === 'custom' && customType) relationship.customType = customType;
await relationship.save();
res.json({ success: true, data: relationship });
} catch (error) {
console.error('Update relationship error:', error);
res.status(500).json({ message: 'Server error' });
}
};
// Remove a relationship
export const removeRelationship = async (req: UserRequest, res: Response): Promise<void> => {
try {
const networkId = req.params.networkId;
const relationshipId = req.params.id;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can remove relationships)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can remove relationships' });
return;
}
const relationship = await Relationship.findOne({
_id: relationshipId,
network: networkId,
});
if (!relationship) {
res.status(404).json({ message: 'Relationship not found' });
return;
}
await relationship.deleteOne(); // Changed from remove() to deleteOne()
res.json({ success: true, message: 'Relationship removed successfully' });
} catch (error) {
console.error('Remove relationship error:', error);
res.status(500).json({ message: 'Server error' });
}
};

View File

@ -1,39 +1,40 @@
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import User from '../models/user.model';
import { UserRequest } from '../types/express';
// JWT secret from environment variables
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 ||
(req.headers.authorization && req.headers.authorization.startsWith('Bearer')
? req.headers.authorization.split(' ')[1]
: null);
if (!token) {
res.status(401).json({ message: 'No token, authorization denied' });
return;
}
// Verify token
const decoded = jwt.verify(token, JWT_SECRET) as { id: string };
// Find user by id
const user = await User.findById(decoded.id).select('-password');
if (!user) {
res.status(401).json({ message: 'User not found' });
return;
}
// Set user in request object
req.user = user;
next();
} catch (error) {
res.status(401).json({ message: 'Token is not valid' });
}
};
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import User from '../models/user.model';
import { UserRequest } from '../types/express';
// JWT secret from environment variables
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 ||
(req.headers.authorization && req.headers.authorization.startsWith('Bearer')
? req.headers.authorization.split(' ')[1]
: null);
if (!token) {
res.status(401).json({ message: 'No token, authorization denied' });
return;
}
// Verify token
const decoded = jwt.verify(token, JWT_SECRET) as { id: string };
// Find user by id
const user = await User.findById(decoded.id).select('-password');
if (!user) {
res.status(401).json({ message: 'User not found' });
return;
}
// Set user in request object
req.user = user;
next();
} catch (error) {
res.status(401).json({ message: 'Token is not valid' });
}
};

View File

@ -1,33 +1,37 @@
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> => {
try {
const networkId = req.params.networkId;
if (!networkId) {
res.status(400).json({ message: 'Network ID is required' });
return;
}
const network = await Network.findById(networkId);
if (!network) {
res.status(404).json({ message: 'Network not found' });
return;
}
// Check if user is the owner or the network is public
if (network.owner.toString() !== req.user?._id.toString() && !network.isPublic) {
res.status(403).json({ message: 'You do not have permission to access this network' });
return;
}
// Add network to the request
req.network = network;
next();
} catch (error) {
res.status(500).json({ message: 'Server error' });
}
};
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> => {
try {
const networkId = req.params.networkId;
if (!networkId) {
res.status(400).json({ message: 'Network ID is required' });
return;
}
const network = await Network.findById(networkId);
if (!network) {
res.status(404).json({ message: 'Network not found' });
return;
}
// Check if user is the owner or the network is public
if (network.owner.toString() !== req.user?._id.toString() && !network.isPublic) {
res.status(403).json({ message: 'You do not have permission to access this network' });
return;
}
// Add network to the request
req.network = network;
next();
} catch (error) {
res.status(500).json({ message: 'Server error' });
}
};

View File

@ -1,36 +1,35 @@
import mongoose, { Document, Schema } from 'mongoose';
export interface INetwork extends Document {
_id: string;
name: string;
description?: string;
owner: mongoose.Types.ObjectId;
isPublic: boolean;
}
const NetworkSchema = new Schema(
{
name: {
type: String,
required: [true, 'Network name is required'],
trim: true,
},
description: {
type: String,
trim: true,
},
owner: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
},
isPublic: {
type: Boolean,
default: false,
},
},
{ timestamps: true }
);
export default mongoose.model<INetwork>('Network', NetworkSchema);
import mongoose, { Document, Schema } from 'mongoose';
export interface INetwork extends Document {
_id: string;
name: string;
description?: string;
owner: mongoose.Types.ObjectId;
isPublic: boolean;
}
const NetworkSchema = new Schema(
{
name: {
type: String,
required: [true, 'Network name is required'],
trim: true,
},
description: {
type: String,
trim: true,
},
owner: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
},
isPublic: {
type: Boolean,
default: false,
},
},
{ timestamps: true }
);
export default mongoose.model<INetwork>('Network', NetworkSchema);

View File

@ -1,52 +1,52 @@
import mongoose, { Document, Schema } from 'mongoose';
export interface IPerson extends Document {
_id: string;
firstName: string;
lastName: string;
birthday?: Date;
network: mongoose.Types.ObjectId;
position: {
x: number;
y: number;
};
}
const PersonSchema = new Schema(
{
firstName: {
type: String,
required: [true, 'First name is required'],
trim: true,
},
lastName: {
type: String,
required: [true, 'Last name is required'],
trim: true,
},
birthday: {
type: Date,
},
network: {
type: Schema.Types.ObjectId,
ref: 'Network',
required: true,
},
position: {
x: {
type: Number,
default: 0,
},
y: {
type: Number,
default: 0,
},
},
},
{ timestamps: true }
);
// Create compound index to ensure uniqueness within a network
PersonSchema.index({ firstName: 1, lastName: 1, network: 1 }, { unique: true });
export default mongoose.model<IPerson>('Person', PersonSchema);
import mongoose, { Document, Schema } from 'mongoose';
export interface IPerson extends Document {
_id: string;
firstName: string;
lastName: string;
birthday?: Date;
network: mongoose.Types.ObjectId;
position: {
x: number;
y: number;
};
}
const PersonSchema = new Schema(
{
firstName: {
type: String,
required: [true, 'First name is required'],
trim: true,
},
lastName: {
type: String,
required: [true, 'Last name is required'],
trim: true,
},
birthday: {
type: Date,
},
network: {
type: Schema.Types.ObjectId,
ref: 'Network',
required: true,
},
position: {
x: {
type: Number,
default: 0,
},
y: {
type: Number,
default: 0,
},
},
},
{ timestamps: true }
);
// Create compound index to ensure uniqueness within a network
PersonSchema.index({ firstName: 1, lastName: 1, network: 1 }, { unique: true });
export default mongoose.model<IPerson>('Person', PersonSchema);

View File

@ -1,48 +1,45 @@
import mongoose, { Document, Schema } from 'mongoose';
export interface IRelationship extends Document {
_id: string;
source: mongoose.Types.ObjectId;
target: mongoose.Types.ObjectId;
type: string;
customType?: string;
network: mongoose.Types.ObjectId;
}
const RelationshipSchema = new Schema(
{
source: {
type: Schema.Types.ObjectId,
ref: 'Person',
required: true,
},
target: {
type: Schema.Types.ObjectId,
ref: 'Person',
required: true,
},
type: {
type: String,
required: [true, 'Relationship type is required'],
enum: ['freund', 'partner', 'familie', 'arbeitskolleg', 'custom'],
},
customType: {
type: String,
trim: true,
},
network: {
type: Schema.Types.ObjectId,
ref: 'Network',
required: true,
},
},
{ timestamps: true }
);
// Create compound index to ensure unique relationships in a network
RelationshipSchema.index(
{ source: 1, target: 1, network: 1 },
{ unique: true }
);
export default mongoose.model<IRelationship>('Relationship', RelationshipSchema);
import mongoose, { Document, Schema } from 'mongoose';
export interface IRelationship extends Document {
_id: string;
source: mongoose.Types.ObjectId;
target: mongoose.Types.ObjectId;
type: string;
customType?: string;
network: mongoose.Types.ObjectId;
}
const RelationshipSchema = new Schema(
{
source: {
type: Schema.Types.ObjectId,
ref: 'Person',
required: true,
},
target: {
type: Schema.Types.ObjectId,
ref: 'Person',
required: true,
},
type: {
type: String,
required: [true, 'Relationship type is required'],
enum: ['freund', 'partner', 'familie', 'arbeitskolleg', 'custom'],
},
customType: {
type: String,
trim: true,
},
network: {
type: Schema.Types.ObjectId,
ref: 'Network',
required: true,
},
},
{ timestamps: true }
);
// Create compound index to ensure unique relationships in a network
RelationshipSchema.index({ source: 1, target: 1, network: 1 }, { unique: true });
export default mongoose.model<IRelationship>('Relationship', RelationshipSchema);

View File

@ -1,54 +1,54 @@
import mongoose, { Document, Schema } from 'mongoose';
import bcrypt from 'bcryptjs';
export interface IUser extends Document {
_id: string;
email: string;
password: string;
username: string;
comparePassword(candidatePassword: string): Promise<boolean>;
}
const UserSchema = new Schema(
{
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
trim: true,
lowercase: true,
},
password: {
type: String,
required: [true, 'Password is required'],
minlength: 6,
},
username: {
type: String,
required: [true, 'Username is required'],
trim: true,
},
},
{ timestamps: true }
);
// Hash password before saving
UserSchema.pre('save', async function(next) {
const user = this;
if (!user.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
next();
} catch (error: any) {
next(error);
}
});
// Compare password method
UserSchema.methods.comparePassword = async function(candidatePassword: string): Promise<boolean> {
return bcrypt.compare(candidatePassword, this.password);
};
export default mongoose.model<IUser>('User', UserSchema);
import mongoose, { Document, Schema } from 'mongoose';
import bcrypt from 'bcryptjs';
export interface IUser extends Document {
_id: string;
email: string;
password: string;
username: string;
comparePassword(candidatePassword: string): Promise<boolean>;
}
const UserSchema = new Schema(
{
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
trim: true,
lowercase: true,
},
password: {
type: String,
required: [true, 'Password is required'],
minlength: 6,
},
username: {
type: String,
required: [true, 'Username is required'],
trim: true,
},
},
{ timestamps: true }
);
// Hash password before saving
UserSchema.pre('save', async function (next) {
const user = this;
if (!user.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(user.password, salt);
next();
} catch (error: any) {
next(error);
}
});
// Compare password method
UserSchema.methods.comparePassword = async function (candidatePassword: string): Promise<boolean> {
return bcrypt.compare(candidatePassword, this.password);
};
export default mongoose.model<IUser>('User', UserSchema);

View File

@ -1,43 +1,43 @@
import express from 'express';
import { check } from 'express-validator';
import * as authController from '../controllers/auth.controller';
import { auth } from '../middleware/auth.middleware';
const router = express.Router();
// @route POST /api/auth/register
// @desc Register a new user
// @access Public
router.post(
'/register',
[
check('email', 'Please include a valid email').isEmail(),
check('password', 'Password must be at least 6 characters').isLength({ min: 6 }),
check('username', 'Username is required').not().isEmpty(),
],
authController.register
);
// @route POST /api/auth/login
// @desc Login user
// @access Public
router.post(
'/login',
[
check('email', 'Please include a valid email').isEmail(),
check('password', 'Password is required').exists(),
],
authController.login
);
// @route POST /api/auth/logout
// @desc Logout user
// @access Private
router.post('/logout', authController.logout);
// @route GET /api/auth/me
// @desc Get current user
// @access Private
router.get('/me', auth, authController.getCurrentUser);
export default router;
import express from 'express';
import { check } from 'express-validator';
import * as authController from '../controllers/auth.controller';
import { auth } from '../middleware/auth.middleware';
const router = express.Router();
// @route POST /api/auth/register
// @desc Register a new user
// @access Public
router.post(
'/register',
[
check('email', 'Please include a valid email').isEmail(),
check('password', 'Password must be at least 6 characters').isLength({ min: 6 }),
check('username', 'Username is required').not().isEmpty(),
],
authController.register
);
// @route POST /api/auth/login
// @desc Login user
// @access Public
router.post(
'/login',
[
check('email', 'Please include a valid email').isEmail(),
check('password', 'Password is required').exists(),
],
authController.login
);
// @route POST /api/auth/logout
// @desc Logout user
// @access Private
router.post('/logout', authController.logout);
// @route GET /api/auth/me
// @desc Get current user
// @access Private
router.get('/me', auth, authController.getCurrentUser);
export default router;

View File

@ -1,48 +1,44 @@
import express from 'express';
import { check } from 'express-validator';
import * as networkController from '../controllers/network.controller';
import { auth } from '../middleware/auth.middleware';
const router = express.Router();
// All routes require authentication
router.use(auth);
// @route GET /api/networks
// @desc Get all networks for current user
// @access Private
router.get('/', networkController.getUserNetworks);
// @route POST /api/networks
// @desc Create a new network
// @access Private
router.post(
'/',
[
check('name', 'Network name is required').not().isEmpty(),
],
networkController.createNetwork
);
// @route GET /api/networks/:id
// @desc Get a specific network
// @access Private
router.get('/:id', networkController.getNetwork);
// @route PUT /api/networks/:id
// @desc Update a network
// @access Private
router.put(
'/:id',
[
check('name', 'Network name is required if provided').optional().not().isEmpty(),
],
networkController.updateNetwork
);
// @route DELETE /api/networks/:id
// @desc Delete a network
// @access Private
router.delete('/:id', networkController.deleteNetwork);
export default router;
import express from 'express';
import { check } from 'express-validator';
import * as networkController from '../controllers/network.controller';
import { auth } from '../middleware/auth.middleware';
const router = express.Router();
// All routes require authentication
router.use(auth);
// @route GET /api/networks
// @desc Get all networks for current user
// @access Private
router.get('/', networkController.getUserNetworks);
// @route POST /api/networks
// @desc Create a new network
// @access Private
router.post(
'/',
[check('name', 'Network name is required').not().isEmpty()],
networkController.createNetwork
);
// @route GET /api/networks/:id
// @desc Get a specific network
// @access Private
router.get('/:id', networkController.getNetwork);
// @route PUT /api/networks/:id
// @desc Update a network
// @access Private
router.put(
'/:id',
[check('name', 'Network name is required if provided').optional().not().isEmpty()],
networkController.updateNetwork
);
// @route DELETE /api/networks/:id
// @desc Delete a network
// @access Private
router.delete('/:id', networkController.deleteNetwork);
export default router;

View File

@ -1,48 +1,48 @@
import express from 'express';
import { check } from 'express-validator';
import * as peopleController from '../controllers/people.controller';
import { auth } from '../middleware/auth.middleware';
import { checkNetworkAccess } from '../middleware/network-access.middleware';
const router = express.Router();
// All routes require authentication and network access check
router.use('/:networkId/people', auth, checkNetworkAccess);
// @route GET /api/networks/:networkId/people
// @desc Get all people in a network
// @access Private
router.get('/:networkId/people', peopleController.getPeople);
// @route POST /api/networks/:networkId/people
// @desc Add a person to the network
// @access Private
router.post(
'/:networkId/people',
[
check('firstName', 'First name is required').not().isEmpty(),
check('lastName', 'Last name is required').not().isEmpty(),
check('birthday', 'Birthday must be a valid date if provided').optional().isISO8601().toDate(),
],
peopleController.addPerson
);
// @route PUT /api/networks/:networkId/people/:id
// @desc Update a person
// @access Private
router.put(
'/:networkId/people/:id',
[
check('firstName', 'First name must not be empty if provided').optional().not().isEmpty(),
check('lastName', 'Last name must not be empty if provided').optional().not().isEmpty(),
check('birthday', 'Birthday must be a valid date if provided').optional().isISO8601().toDate(),
],
peopleController.updatePerson
);
// @route DELETE /api/networks/:networkId/people/:id
// @desc Remove a person from the network
// @access Private
router.delete('/:networkId/people/:id', peopleController.removePerson);
export default router;
import express from 'express';
import { check } from 'express-validator';
import * as peopleController from '../controllers/people.controller';
import { auth } from '../middleware/auth.middleware';
import { checkNetworkAccess } from '../middleware/network-access.middleware';
const router = express.Router();
// All routes require authentication and network access check
router.use('/:networkId/people', auth, checkNetworkAccess);
// @route GET /api/networks/:networkId/people
// @desc Get all people in a network
// @access Private
router.get('/:networkId/people', peopleController.getPeople);
// @route POST /api/networks/:networkId/people
// @desc Add a person to the network
// @access Private
router.post(
'/:networkId/people',
[
check('firstName', 'First name is required').not().isEmpty(),
check('lastName', 'Last name is required').not().isEmpty(),
check('birthday', 'Birthday must be a valid date if provided').optional().isISO8601().toDate(),
],
peopleController.addPerson
);
// @route PUT /api/networks/:networkId/people/:id
// @desc Update a person
// @access Private
router.put(
'/:networkId/people/:id',
[
check('firstName', 'First name must not be empty if provided').optional().not().isEmpty(),
check('lastName', 'Last name must not be empty if provided').optional().not().isEmpty(),
check('birthday', 'Birthday must be a valid date if provided').optional().isISO8601().toDate(),
],
peopleController.updatePerson
);
// @route DELETE /api/networks/:networkId/people/:id
// @desc Remove a person from the network
// @access Private
router.delete('/:networkId/people/:id', peopleController.removePerson);
export default router;

View File

@ -1,56 +1,62 @@
import express from 'express';
import { check } from 'express-validator';
import * as relationshipController from '../controllers/relationship.controller';
import { auth } from '../middleware/auth.middleware';
import { checkNetworkAccess } from '../middleware/network-access.middleware';
const router = express.Router();
// All routes require authentication and network access check
router.use('/:networkId/relationships', auth, checkNetworkAccess);
// @route GET /api/networks/:networkId/relationships
// @desc Get all relationships in a network
// @access Private
router.get('/:networkId/relationships', relationshipController.getRelationships);
// @route POST /api/networks/:networkId/relationships
// @desc Add a relationship to the network
// @access Private
router.post(
'/:networkId/relationships',
[
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('customType', 'Custom type is required when type is custom')
.if(check('type').equals('custom'))
.not()
.isEmpty(),
],
relationshipController.addRelationship
);
// @route PUT /api/networks/:networkId/relationships/:id
// @desc Update a relationship
// @access Private
router.put(
'/:networkId/relationships/:id',
[
check('type', 'Relationship type must be valid if provided')
.optional()
.isIn(['freund', 'partner', 'familie', 'arbeitskolleg', 'custom']),
check('customType', 'Custom type is required when type is custom')
.if(check('type').equals('custom'))
.not()
.isEmpty(),
],
relationshipController.updateRelationship
);
// @route DELETE /api/networks/:networkId/relationships/:id
// @desc Remove a relationship
// @access Private
router.delete('/:networkId/relationships/:id', relationshipController.removeRelationship);
export default router;
import express from 'express';
import { check } from 'express-validator';
import * as relationshipController from '../controllers/relationship.controller';
import { auth } from '../middleware/auth.middleware';
import { checkNetworkAccess } from '../middleware/network-access.middleware';
const router = express.Router();
// All routes require authentication and network access check
router.use('/:networkId/relationships', auth, checkNetworkAccess);
// @route GET /api/networks/:networkId/relationships
// @desc Get all relationships in a network
// @access Private
router.get('/:networkId/relationships', relationshipController.getRelationships);
// @route POST /api/networks/:networkId/relationships
// @desc Add a relationship to the network
// @access Private
router.post(
'/:networkId/relationships',
[
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('customType', 'Custom type is required when type is custom')
.if(check('type').equals('custom'))
.not()
.isEmpty(),
],
relationshipController.addRelationship
);
// @route PUT /api/networks/:networkId/relationships/:id
// @desc Update a relationship
// @access Private
router.put(
'/:networkId/relationships/:id',
[
check('type', 'Relationship type must be valid if provided')
.optional()
.isIn(['freund', 'partner', 'familie', 'arbeitskolleg', 'custom']),
check('customType', 'Custom type is required when type is custom')
.if(check('type').equals('custom'))
.not()
.isEmpty(),
],
relationshipController.updateRelationship
);
// @route DELETE /api/networks/:networkId/relationships/:id
// @desc Remove a relationship
// @access Private
router.delete('/:networkId/relationships/:id', relationshipController.removeRelationship);
export default router;

View File

@ -1,15 +1,15 @@
import app from './app';
import connectDB from './config/db';
import dotenv from 'dotenv';
dotenv.config();
const PORT = process.env.PORT || 5000;
// Connect to MongoDB
connectDB();
// Start server
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
import app from './app';
import connectDB from './config/db';
import dotenv from 'dotenv';
dotenv.config();
const PORT = process.env.PORT || 5000;
// Connect to MongoDB
connectDB();
// Start server
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

View File

@ -1,9 +1,9 @@
import { Request } from 'express';
import { IUser } from '../models/user.model';
import { INetwork } from '../models/network.model';
import { Document } from 'mongoose';
export interface UserRequest extends Request {
user?: IUser;
network?: INetwork;
}
import { Request } from 'express';
import { IUser } from '../models/user.model';
import { INetwork } from '../models/network.model';
import { Document } from 'mongoose';
export interface UserRequest extends Request {
user?: IUser;
network?: INetwork;
}

2316
yarn.lock

File diff suppressed because it is too large Load Diff