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": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "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": "", "author": "",
"license": "ISC", "license": "ISC",
@ -22,6 +24,7 @@
"@types/react": "^19.1.2", "@types/react": "^19.1.2",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^4.4.0", "@vitejs/plugin-react": "^4.4.0",
"prettier": "^3.5.3",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vite": "^6.2.6", "vite": "^6.2.6",

View File

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

View File

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

View File

@ -1,64 +1,72 @@
import axios from 'axios'; import axios from 'axios';
const protocol = window.location.protocol; const protocol = window.location.protocol;
const hostname = window.location.hostname; const hostname = window.location.hostname;
const port = window.location.port; const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api'; const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Types // Types
export interface NetworkOwner { export interface NetworkOwner {
_id: string; _id: string;
username: string; username: string;
} }
export interface Network { export interface Network {
_id: string; _id: string;
name: string; name: string;
description?: string; description?: string;
owner: string | NetworkOwner; // Can be either string ID or populated object owner: string | NetworkOwner; // Can be either string ID or populated object
isPublic: boolean; isPublic: boolean;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
} }
export interface CreateNetworkData { export interface CreateNetworkData {
name: string; name: string;
description?: string; description?: string;
isPublic?: boolean; isPublic?: boolean;
} }
export interface UpdateNetworkData { export interface UpdateNetworkData {
name?: string; name?: string;
description?: string; description?: string;
isPublic?: boolean; isPublic?: boolean;
} }
// Get all networks for current user // Get all networks for current user
export const getUserNetworks = async (): Promise<Network[]> => { export const getUserNetworks = async (): Promise<Network[]> => {
const response = await axios.get<{ success: boolean; data: Network[] }>(`${API_URL}/networks`); const response = await axios.get<{ success: boolean; data: Network[] }>(`${API_URL}/networks`);
return response.data.data; return response.data.data;
}; };
// Create a new network // Create a new network
export const createNetwork = async (data: CreateNetworkData): Promise<Network> => { export const createNetwork = async (data: CreateNetworkData): Promise<Network> => {
const response = await axios.post<{ success: boolean; data: Network }>(`${API_URL}/networks`, data); const response = await axios.post<{ success: boolean; data: Network }>(
return response.data.data; `${API_URL}/networks`,
}; data
);
// Get a specific network return response.data.data;
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; // Get a specific network
}; export const getNetwork = async (id: string): Promise<Network> => {
const response = await axios.get<{ success: boolean; data: Network }>(
// Update a network `${API_URL}/networks/${id}`
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;
return response.data.data; };
};
// Update a network
// Delete a network export const updateNetwork = async (id: string, data: UpdateNetworkData): Promise<Network> => {
export const deleteNetwork = async (id: string): Promise<void> => { const response = await axios.put<{ success: boolean; data: Network }>(
await axios.delete(`${API_URL}/networks/${id}`); `${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'; import axios from 'axios';
const protocol = window.location.protocol; const protocol = window.location.protocol;
const hostname = window.location.hostname; const hostname = window.location.hostname;
const port = window.location.port; const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api'; const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Types // Types
export interface Person { export interface Person {
_id: string; _id: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
birthday?: string; birthday?: string;
network: string; network: string;
position: { position: {
x: number; x: number;
y: number; y: number;
}; };
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
} }
export interface CreatePersonData { export interface CreatePersonData {
firstName: string; firstName: string;
lastName: string; lastName: string;
birthday?: string; birthday?: string;
position?: { position?: {
x: number; x: number;
y: number; y: number;
}; };
} }
export interface UpdatePersonData { export interface UpdatePersonData {
firstName?: string; firstName?: string;
lastName?: string; lastName?: string;
birthday?: string | null; birthday?: string | null;
position?: { position?: {
x: number; x: number;
y: number; y: number;
}; };
} }
// Get all people in a network // Get all people in a network
export const getPeople = async (networkId: string): Promise<Person[]> => { export const getPeople = async (networkId: string): Promise<Person[]> => {
const response = await axios.get<{ success: boolean; data: Person[] }>( const response = await axios.get<{ success: boolean; data: Person[] }>(
`${API_URL}/networks/${networkId}/people` `${API_URL}/networks/${networkId}/people`
); );
return response.data.data; return response.data.data;
}; };
// Add a person to the network // Add a person to the network
export const addPerson = async (networkId: string, data: CreatePersonData): Promise<Person> => { export const addPerson = async (networkId: string, data: CreatePersonData): Promise<Person> => {
const response = await axios.post<{ success: boolean; data: Person }>( const response = await axios.post<{ success: boolean; data: Person }>(
`${API_URL}/networks/${networkId}/people`, `${API_URL}/networks/${networkId}/people`,
data data
); );
return response.data.data; return response.data.data;
}; };
// Update a person // Update a person
export const updatePerson = async ( export const updatePerson = async (
networkId: string, networkId: string,
personId: string, personId: string,
data: UpdatePersonData data: UpdatePersonData
): Promise<Person> => { ): Promise<Person> => {
const response = await axios.put<{ success: boolean; data: Person }>( const response = await axios.put<{ success: boolean; data: Person }>(
`${API_URL}/networks/${networkId}/people/${personId}`, `${API_URL}/networks/${networkId}/people/${personId}`,
data data
); );
return response.data.data; return response.data.data;
}; };
// Remove a person from the network // Remove a person from the network
export const removePerson = async (networkId: string, personId: string): Promise<void> => { export const removePerson = async (networkId: string, personId: string): Promise<void> => {
await axios.delete(`${API_URL}/networks/${networkId}/people/${personId}`); await axios.delete(`${API_URL}/networks/${networkId}/people/${personId}`);
}; };

View File

@ -1,69 +1,72 @@
import axios from 'axios'; import axios from 'axios';
const protocol = window.location.protocol; const protocol = window.location.protocol;
const hostname = window.location.hostname; const hostname = window.location.hostname;
const port = window.location.port; const port = window.location.port;
const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api'; const API_URL = protocol + '//' + hostname + (port ? ':' + port : '') + '/api';
// Types // Types
export interface Relationship { export interface Relationship {
_id: string; _id: string;
source: string; source: string;
target: string; target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom'; type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string; customType?: string;
network: string; network: string;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
} }
export interface CreateRelationshipData { export interface CreateRelationshipData {
source: string; source: string;
target: string; target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom'; type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string; customType?: string;
} }
export interface UpdateRelationshipData { export interface UpdateRelationshipData {
type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom'; type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string; customType?: string;
} }
// Get all relationships in a network // Get all relationships in a network
export const getRelationships = async (networkId: string): Promise<Relationship[]> => { export const getRelationships = async (networkId: string): Promise<Relationship[]> => {
const response = await axios.get<{ success: boolean; data: Relationship[] }>( const response = await axios.get<{ success: boolean; data: Relationship[] }>(
`${API_URL}/networks/${networkId}/relationships` `${API_URL}/networks/${networkId}/relationships`
); );
return response.data.data; return response.data.data;
}; };
// Add a relationship to the network // Add a relationship to the network
export const addRelationship = async ( export const addRelationship = async (
networkId: string, networkId: string,
data: CreateRelationshipData data: CreateRelationshipData
): Promise<Relationship> => { ): Promise<Relationship> => {
const response = await axios.post<{ success: boolean; data: Relationship }>( const response = await axios.post<{ success: boolean; data: Relationship }>(
`${API_URL}/networks/${networkId}/relationships`, `${API_URL}/networks/${networkId}/relationships`,
data data
); );
return response.data.data; return response.data.data;
}; };
// Update a relationship // Update a relationship
export const updateRelationship = async ( export const updateRelationship = async (
networkId: string, networkId: string,
relationshipId: string, relationshipId: string,
data: UpdateRelationshipData data: UpdateRelationshipData
): Promise<Relationship> => { ): Promise<Relationship> => {
const response = await axios.put<{ success: boolean; data: Relationship }>( const response = await axios.put<{ success: boolean; data: Relationship }>(
`${API_URL}/networks/${networkId}/relationships/${relationshipId}`, `${API_URL}/networks/${networkId}/relationships/${relationshipId}`,
data data
); );
return response.data.data; return response.data.data;
}; };
// Remove a relationship // Remove a relationship
export const removeRelationship = async (networkId: string, relationshipId: string): Promise<void> => { export const removeRelationship = async (
await axios.delete(`${API_URL}/networks/${networkId}/relationships/${relationshipId}`); 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 React, { useState } from 'react';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
const Login: React.FC = () => { const Login: React.FC = () => {
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { login } = useAuth(); const { login } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setError(null); setError(null);
setLoading(true); setLoading(true);
try { try {
await login({ email, password }); await login({ email, password });
navigate('/networks'); navigate('/networks');
} catch (err: any) { } catch (err: any) {
setError(err.response?.data?.message || 'Login failed. Please check your credentials.'); setError(err.response?.data?.message || 'Login failed. Please check your credentials.');
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
return ( return (
<div className="flex items-center justify-center min-h-screen bg-gray-100"> <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"> <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> <h2 className="text-2xl font-bold mb-6 text-center">Login</h2>
{error && ( {error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4"> <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error} {error}
</div> </div>
)} )}
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="mb-4"> <div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email"> <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="email">
Email Email
</label> </label>
<input <input
id="email" id="email"
type="email" type="email"
className="w-full px-3 py-2 border border-gray-300 rounded" className="w-full px-3 py-2 border border-gray-300 rounded"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={e => setEmail(e.target.value)}
required required
/> />
</div> </div>
<div className="mb-6"> <div className="mb-6">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password"> <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
Password Password
</label> </label>
<input <input
id="password" id="password"
type="password" type="password"
className="w-full px-3 py-2 border border-gray-300 rounded" className="w-full px-3 py-2 border border-gray-300 rounded"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={e => setPassword(e.target.value)}
required required
/> />
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<button <button
type="submit" 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" 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} disabled={loading}
> >
{loading ? 'Logging in...' : 'Login'} {loading ? 'Logging in...' : 'Login'}
</button> </button>
<a <a
href="/register" href="/register"
className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800"
> >
Register Register
</a> </a>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
); );
}; };
export default Login; export default Login;

View File

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

View File

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

View File

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

View File

@ -1,70 +1,78 @@
import React, { createContext, useState, useEffect, useContext, ReactNode } from 'react'; import React, { createContext, useState, useEffect, useContext, ReactNode } from 'react';
import { User, getCurrentUser, login as apiLogin, register as apiRegister, logout as apiLogout, LoginData, RegisterData } from '../api/auth'; import {
User,
interface AuthContextProps { getCurrentUser,
user: User | null; login as apiLogin,
loading: boolean; register as apiRegister,
login: (data: LoginData) => Promise<void>; logout as apiLogout,
register: (data: RegisterData) => Promise<void>; LoginData,
logout: () => Promise<void>; RegisterData,
} } from '../api/auth';
const AuthContext = createContext<AuthContextProps>({} as AuthContextProps); interface AuthContextProps {
user: User | null;
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => { loading: boolean;
const [user, setUser] = useState<User | null>(null); login: (data: LoginData) => Promise<void>;
const [loading, setLoading] = useState(true); register: (data: RegisterData) => Promise<void>;
logout: () => Promise<void>;
useEffect(() => { }
const loadUser = async () => {
try { const AuthContext = createContext<AuthContextProps>({} as AuthContextProps);
const currentUser = await getCurrentUser();
setUser(currentUser); export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
} catch (error) { const [user, setUser] = useState<User | null>(null);
console.error('Error loading user:', error); const [loading, setLoading] = useState(true);
} finally {
setLoading(false); useEffect(() => {
} const loadUser = async () => {
}; try {
const currentUser = await getCurrentUser();
loadUser(); setUser(currentUser);
}, []); } catch (error) {
console.error('Error loading user:', error);
const login = async (data: LoginData) => { } finally {
try { setLoading(false);
const loggedInUser = await apiLogin(data); }
setUser(loggedInUser); };
} catch (error) {
console.error('Login error:', error); loadUser();
throw error; }, []);
}
}; const login = async (data: LoginData) => {
try {
const register = async (data: RegisterData) => { const loggedInUser = await apiLogin(data);
try { setUser(loggedInUser);
const newUser = await apiRegister(data); } catch (error) {
setUser(newUser); console.error('Login error:', error);
} catch (error) { throw error;
console.error('Registration error:', error); }
throw error; };
}
}; const register = async (data: RegisterData) => {
try {
const logout = async () => { const newUser = await apiRegister(data);
try { setUser(newUser);
await apiLogout(); } catch (error) {
setUser(null); console.error('Registration error:', error);
} catch (error) { throw error;
console.error('Logout error:', error); }
throw error; };
}
}; const logout = async () => {
try {
return ( await apiLogout();
<AuthContext.Provider value={{ user, loading, login, register, logout }}> setUser(null);
{children} } catch (error) {
</AuthContext.Provider> console.error('Logout error:', error);
); throw error;
}; }
};
export const useAuth = () => useContext(AuthContext);
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 React, { createContext, useState, useEffect, useContext, ReactNode } from 'react';
import { import {
Network, Network,
getUserNetworks, getUserNetworks,
createNetwork as apiCreateNetwork, createNetwork as apiCreateNetwork,
updateNetwork as apiUpdateNetwork, updateNetwork as apiUpdateNetwork,
deleteNetwork as apiDeleteNetwork, deleteNetwork as apiDeleteNetwork,
CreateNetworkData, CreateNetworkData,
UpdateNetworkData UpdateNetworkData,
} from '../api/network'; } from '../api/network';
import { useAuth } from './AuthContext'; import { useAuth } from './AuthContext';
interface NetworkContextProps { interface NetworkContextProps {
networks: Network[]; networks: Network[];
loading: boolean; loading: boolean;
error: string | null; error: string | null;
createNetwork: (data: CreateNetworkData) => Promise<Network>; createNetwork: (data: CreateNetworkData) => Promise<Network>;
updateNetwork: (id: string, data: UpdateNetworkData) => Promise<Network>; updateNetwork: (id: string, data: UpdateNetworkData) => Promise<Network>;
deleteNetwork: (id: string) => Promise<void>; deleteNetwork: (id: string) => Promise<void>;
refreshNetworks: () => Promise<void>; refreshNetworks: () => Promise<void>;
} }
const NetworkContext = createContext<NetworkContextProps>({} as NetworkContextProps); const NetworkContext = createContext<NetworkContextProps>({} as NetworkContextProps);
export const NetworkProvider: React.FC<{ children: ReactNode }> = ({ children }) => { export const NetworkProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [networks, setNetworks] = useState<Network[]>([]); const [networks, setNetworks] = useState<Network[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const { user } = useAuth(); const { user } = useAuth();
const loadNetworks = async () => { const loadNetworks = async () => {
if (!user) { if (!user) {
setNetworks([]); setNetworks([]);
setLoading(false); setLoading(false);
return; return;
} }
try { try {
setLoading(true); setLoading(true);
setError(null); setError(null);
const fetchedNetworks = await getUserNetworks(); const fetchedNetworks = await getUserNetworks();
setNetworks(fetchedNetworks); setNetworks(fetchedNetworks);
} catch (err: any) { } catch (err: any) {
setError(err.message || 'Failed to load networks'); setError(err.message || 'Failed to load networks');
console.error('Error loading networks:', err); console.error('Error loading networks:', err);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
useEffect(() => { useEffect(() => {
loadNetworks(); loadNetworks();
}, [user]); }, [user]);
const createNetwork = async (data: CreateNetworkData): Promise<Network> => { const createNetwork = async (data: CreateNetworkData): Promise<Network> => {
try { try {
const newNetwork = await apiCreateNetwork(data); const newNetwork = await apiCreateNetwork(data);
setNetworks([...networks, newNetwork]); setNetworks([...networks, newNetwork]);
return newNetwork; return newNetwork;
} catch (err: any) { } catch (err: any) {
setError(err.message || 'Failed to create network'); setError(err.message || 'Failed to create network');
throw err; throw err;
} }
}; };
const updateNetwork = async (id: string, data: UpdateNetworkData): Promise<Network> => { const updateNetwork = async (id: string, data: UpdateNetworkData): Promise<Network> => {
try { try {
const updatedNetwork = await apiUpdateNetwork(id, data); const updatedNetwork = await apiUpdateNetwork(id, data);
setNetworks(networks.map(network => setNetworks(networks.map(network => (network._id === id ? updatedNetwork : network)));
network._id === id ? updatedNetwork : network return updatedNetwork;
)); } catch (err: any) {
return updatedNetwork; setError(err.message || 'Failed to update network');
} catch (err: any) { throw err;
setError(err.message || 'Failed to update network'); }
throw err; };
}
}; const deleteNetwork = async (id: string): Promise<void> => {
try {
const deleteNetwork = async (id: string): Promise<void> => { await apiDeleteNetwork(id);
try { setNetworks(networks.filter(network => network._id !== id));
await apiDeleteNetwork(id); } catch (err: any) {
setNetworks(networks.filter(network => network._id !== id)); setError(err.message || 'Failed to delete network');
} catch (err: any) { throw err;
setError(err.message || 'Failed to delete network'); }
throw err; };
}
}; const refreshNetworks = async (): Promise<void> => {
await loadNetworks();
const refreshNetworks = async (): Promise<void> => { };
await loadNetworks();
}; return (
<NetworkContext.Provider
return ( value={{
<NetworkContext.Provider networks,
value={{ loading,
networks, error,
loading, createNetwork,
error, updateNetwork,
createNetwork, deleteNetwork,
updateNetwork, refreshNetworks,
deleteNetwork, }}
refreshNetworks >
}} {children}
> </NetworkContext.Provider>
{children} );
</NetworkContext.Provider> };
);
}; export const useNetworks = () => useContext(NetworkContext);
export const useNetworks = () => useContext(NetworkContext);

View File

@ -1,210 +1,221 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { Person, getPeople, addPerson, updatePerson, removePerson } from '../api/people'; import { Person, getPeople, addPerson, updatePerson, removePerson } from '../api/people';
import { Relationship, getRelationships, addRelationship, updateRelationship, removeRelationship } from '../api/relationships'; import {
Relationship,
interface PersonNode extends Person { getRelationships,
// Additional properties needed for the visualization addRelationship,
id: string; // Alias for _id to work with the visualization updateRelationship,
} removeRelationship,
} from '../api/relationships';
interface RelationshipEdge extends Relationship {
// Additional properties needed for the visualization interface PersonNode extends Person {
id: string; // Alias for _id to work with the visualization // 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) => { interface RelationshipEdge extends Relationship {
const [people, setPeople] = useState<PersonNode[]>([]); // Additional properties needed for the visualization
const [relationships, setRelationships] = useState<RelationshipEdge[]>([]); id: string; // Alias for _id to work with the visualization
const [loading, setLoading] = useState(true); }
const [error, setError] = useState<string | null>(null);
// Custom hook to manage friendship network data
// Load network data export const useFriendshipNetwork = (networkId: string | null) => {
const loadNetworkData = useCallback(async () => { const [people, setPeople] = useState<PersonNode[]>([]);
if (!networkId) { const [relationships, setRelationships] = useState<RelationshipEdge[]>([]);
setPeople([]); const [loading, setLoading] = useState(true);
setRelationships([]); const [error, setError] = useState<string | null>(null);
setLoading(false);
return; // Load network data
} const loadNetworkData = useCallback(async () => {
if (!networkId) {
try { setPeople([]);
setLoading(true); setRelationships([]);
setError(null); setLoading(false);
return;
// Fetch people and relationships in parallel }
const [peopleData, relationshipsData] = await Promise.all([
getPeople(networkId), try {
getRelationships(networkId) setLoading(true);
]); setError(null);
// Transform to add the id property needed by the visualization // Fetch people and relationships in parallel
const peopleNodes: PersonNode[] = peopleData.map(person => ({ const [peopleData, relationshipsData] = await Promise.all([
...person, getPeople(networkId),
id: person._id getRelationships(networkId),
})); ]);
const relationshipEdges: RelationshipEdge[] = relationshipsData.map(rel => ({ // Transform to add the id property needed by the visualization
...rel, const peopleNodes: PersonNode[] = peopleData.map(person => ({
id: rel._id ...person,
})); id: person._id,
}));
setPeople(peopleNodes);
setRelationships(relationshipEdges); const relationshipEdges: RelationshipEdge[] = relationshipsData.map(rel => ({
} catch (err: any) { ...rel,
setError(err.message || 'Failed to load network data'); id: rel._id,
console.error('Error loading network data:', err); }));
} finally {
setLoading(false); setPeople(peopleNodes);
} setRelationships(relationshipEdges);
}, [networkId]); } catch (err: any) {
setError(err.message || 'Failed to load network data');
useEffect(() => { console.error('Error loading network data:', err);
loadNetworkData(); } finally {
}, [loadNetworkData]); setLoading(false);
}
// Add a new person }, [networkId]);
const createPerson = async (personData: {
firstName: string; useEffect(() => {
lastName: string; loadNetworkData();
birthday?: string; }, [loadNetworkData]);
position?: { x: number; y: number }
}): Promise<PersonNode> => { // Add a new person
if (!networkId) throw new Error('No network selected'); const createPerson = async (personData: {
firstName: string;
try { lastName: string;
const newPerson = await addPerson(networkId, personData); birthday?: string;
const newPersonNode: PersonNode = { ...newPerson, id: newPerson._id }; position?: { x: number; y: number };
setPeople([...people, newPersonNode]); }): Promise<PersonNode> => {
return newPersonNode; if (!networkId) throw new Error('No network selected');
} catch (err: any) {
setError(err.message || 'Failed to create person'); try {
throw err; const newPerson = await addPerson(networkId, personData);
} const newPersonNode: PersonNode = { ...newPerson, id: newPerson._id };
}; setPeople([...people, newPersonNode]);
return newPersonNode;
// Update a person } catch (err: any) {
const updatePersonData = async ( setError(err.message || 'Failed to create person');
personId: string, throw err;
personData: { }
firstName?: string; };
lastName?: string;
birthday?: string | null; // Update a person
position?: { x: number; y: number } const updatePersonData = async (
} personId: string,
): Promise<PersonNode> => { personData: {
if (!networkId) throw new Error('No network selected'); firstName?: string;
lastName?: string;
try { birthday?: string | null;
const updatedPerson = await updatePerson(networkId, personId, personData); position?: { x: number; y: number };
const updatedPersonNode: PersonNode = { ...updatedPerson, id: updatedPerson._id }; }
): Promise<PersonNode> => {
setPeople(people.map(person => if (!networkId) throw new Error('No network selected');
person._id === personId ? updatedPersonNode : person
)); try {
const updatedPerson = await updatePerson(networkId, personId, personData);
return updatedPersonNode; const updatedPersonNode: PersonNode = { ...updatedPerson, id: updatedPerson._id };
} catch (err: any) {
setError(err.message || 'Failed to update person'); setPeople(people.map(person => (person._id === personId ? updatedPersonNode : person)));
throw err;
} return updatedPersonNode;
}; } catch (err: any) {
setError(err.message || 'Failed to update person');
// Remove a person throw err;
const deletePerson = async (personId: string): Promise<void> => { }
if (!networkId) throw new Error('No network selected'); };
try { // Remove a person
await removePerson(networkId, personId); const deletePerson = async (personId: string): Promise<void> => {
if (!networkId) throw new Error('No network selected');
// Remove the person
setPeople(people.filter(person => person._id !== personId)); try {
await removePerson(networkId, personId);
// Remove all relationships involving this person
setRelationships(relationships.filter( // Remove the person
rel => rel.source !== personId && rel.target !== personId setPeople(people.filter(person => person._id !== personId));
));
} catch (err: any) { // Remove all relationships involving this person
setError(err.message || 'Failed to delete person'); setRelationships(
throw err; relationships.filter(rel => rel.source !== personId && rel.target !== personId)
} );
}; } catch (err: any) {
setError(err.message || 'Failed to delete person');
// Create a relationship throw err;
const createRelationship = async (relationshipData: { }
source: string; };
target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom'; // Create a relationship
customType?: string; const createRelationship = async (relationshipData: {
}): Promise<RelationshipEdge> => { source: string;
if (!networkId) throw new Error('No network selected'); target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
try { customType?: string;
const newRelationship = await addRelationship(networkId, relationshipData); }): Promise<RelationshipEdge> => {
const newRelationshipEdge: RelationshipEdge = { ...newRelationship, id: newRelationship._id }; if (!networkId) throw new Error('No network selected');
setRelationships([...relationships, newRelationshipEdge]); try {
return newRelationshipEdge; const newRelationship = await addRelationship(networkId, relationshipData);
} catch (err: any) { const newRelationshipEdge: RelationshipEdge = { ...newRelationship, id: newRelationship._id };
setError(err.message || 'Failed to create relationship');
throw err; setRelationships([...relationships, newRelationshipEdge]);
} return newRelationshipEdge;
}; } catch (err: any) {
setError(err.message || 'Failed to create relationship');
// Update a relationship throw err;
const updateRelationshipData = async ( }
relationshipId: string, };
relationshipData: {
type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom'; // Update a relationship
customType?: string; const updateRelationshipData = async (
} relationshipId: string,
): Promise<RelationshipEdge> => { relationshipData: {
if (!networkId) throw new Error('No network selected'); type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string;
try { }
const updatedRelationship = await updateRelationship(networkId, relationshipId, relationshipData); ): Promise<RelationshipEdge> => {
const updatedRelationshipEdge: RelationshipEdge = { ...updatedRelationship, id: updatedRelationship._id }; if (!networkId) throw new Error('No network selected');
setRelationships(relationships.map(rel => try {
rel._id === relationshipId ? updatedRelationshipEdge : rel const updatedRelationship = await updateRelationship(
)); networkId,
relationshipId,
return updatedRelationshipEdge; relationshipData
} catch (err: any) { );
setError(err.message || 'Failed to update relationship'); const updatedRelationshipEdge: RelationshipEdge = {
throw err; ...updatedRelationship,
} id: updatedRelationship._id,
}; };
// Remove a relationship setRelationships(
const deleteRelationship = async (relationshipId: string): Promise<void> => { relationships.map(rel => (rel._id === relationshipId ? updatedRelationshipEdge : rel))
if (!networkId) throw new Error('No network selected'); );
try { return updatedRelationshipEdge;
await removeRelationship(networkId, relationshipId); } catch (err: any) {
setRelationships(relationships.filter(rel => rel._id !== relationshipId)); setError(err.message || 'Failed to update relationship');
} catch (err: any) { throw err;
setError(err.message || 'Failed to delete relationship'); }
throw err; };
}
}; // Remove a relationship
const deleteRelationship = async (relationshipId: string): Promise<void> => {
// Refresh the network data if (!networkId) throw new Error('No network selected');
const refreshNetwork = async (): Promise<void> => {
await loadNetworkData(); try {
}; await removeRelationship(networkId, relationshipId);
setRelationships(relationships.filter(rel => rel._id !== relationshipId));
return { } catch (err: any) {
people, setError(err.message || 'Failed to delete relationship');
relationships, throw err;
loading, }
error, };
createPerson,
updatePerson: updatePersonData, // Refresh the network data
deletePerson, const refreshNetwork = async (): Promise<void> => {
createRelationship, await loadNetworkData();
updateRelationship: updateRelationshipData, };
deleteRelationship,
refreshNetwork 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 React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import App from './App'; import App from './App';
// Create root and render the App component into the HTML element with ID 'root' // Create root and render the App component into the HTML element with ID 'root'
const rootElement = document.getElementById('root'); const rootElement = document.getElementById('root');
if (rootElement) { if (rootElement) {
const root = ReactDOM.createRoot(rootElement); const root = ReactDOM.createRoot(rootElement);
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode> </React.StrictMode>
); );
} else { } else {
console.error('Root element not found'); 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": { "scripts": {
"start": "node dist/server.js", "start": "node dist/server.js",
"dev": "nodemon --exec ts-node src/server.ts", "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": { "repository": {
"type": "git", "type": "git",
@ -38,6 +41,7 @@
"@types/mongoose": "^5.11.97", "@types/mongoose": "^5.11.97",
"@types/node": "^22.14.1", "@types/node": "^22.14.1",
"nodemon": "^3.1.9", "nodemon": "^3.1.9",
"prettier": "^3.5.3",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.8.3" "typescript": "^5.8.3"
} }

View File

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

View File

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

View File

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

View File

@ -1,169 +1,166 @@
import { Response } from 'express'; import { Response } from 'express';
import Network from '../models/network.model'; import Network from '../models/network.model';
import { UserRequest } from '../types/express'; import { UserRequest } from '../types/express';
import { validationResult } from 'express-validator'; import { validationResult } from 'express-validator';
// Get all networks for current user and all public networks // Get all networks for current user and all public networks
export const getUserNetworks = async (req: UserRequest, res: Response): Promise<void> => { export const getUserNetworks = async (req: UserRequest, res: Response): Promise<void> => {
try { try {
if (!req.user) { if (!req.user) {
res.status(401).json({ message: 'Not authorized' }); res.status(401).json({ message: 'Not authorized' });
return; return;
} }
// Find networks that either: // Find networks that either:
// 1. Belong to the current user, OR // 1. Belong to the current user, OR
// 2. Are public networks (created by any user) // 2. Are public networks (created by any user)
const networks = await Network.find({ const networks = await Network.find({
$or: [ $or: [{ owner: req.user._id }, { isPublic: true }],
{ owner: req.user._id }, }).populate('owner', 'username _id'); // Populate owner field with username
{ isPublic: true }
] res.json({ success: true, data: networks });
}).populate('owner', 'username _id'); // Populate owner field with username } catch (error) {
console.error('Get networks error:', error);
res.json({ success: true, data: networks }); res.status(500).json({ message: 'Server error' });
} 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 {
// Create a new network // Validate request
export const createNetwork = async (req: UserRequest, res: Response): Promise<void> => { const errors = validationResult(req);
try { if (!errors.isEmpty()) {
// Validate request res.status(400).json({ errors: errors.array() });
const errors = validationResult(req); return;
if (!errors.isEmpty()) { }
res.status(400).json({ errors: errors.array() });
return; if (!req.user) {
} res.status(401).json({ message: 'Not authorized' });
return;
if (!req.user) { }
res.status(401).json({ message: 'Not authorized' });
return; const { name, description, isPublic } = req.body;
}
const network = new Network({
const { name, description, isPublic } = req.body; name,
description,
const network = new Network({ owner: req.user._id,
name, isPublic: isPublic || false,
description, });
owner: req.user._id,
isPublic: isPublic || false, await network.save();
});
res.status(201).json({ success: true, data: network });
await network.save(); } catch (error) {
console.error('Create network error:', error);
res.status(201).json({ success: true, data: network }); res.status(500).json({ message: 'Server error' });
} 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 {
// Get a specific network const networkId = req.params.id;
export const getNetwork = async (req: UserRequest, res: Response): Promise<void> => {
try { if (!req.user) {
const networkId = req.params.id; res.status(401).json({ message: 'Not authorized' });
return;
if (!req.user) { }
res.status(401).json({ message: 'Not authorized' });
return; const network = await Network.findById(networkId).populate('owner', 'username _id');
}
if (!network) {
const network = await Network.findById(networkId).populate('owner', 'username _id'); res.status(404).json({ message: 'Network not found' });
return;
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' });
// Check if user is owner or network is public return;
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.json({ success: true, data: network }); res.status(500).json({ message: 'Server error' });
} 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 {
// Update a network // Validate request
export const updateNetwork = async (req: UserRequest, res: Response): Promise<void> => { const errors = validationResult(req);
try { if (!errors.isEmpty()) {
// Validate request res.status(400).json({ errors: errors.array() });
const errors = validationResult(req); return;
if (!errors.isEmpty()) { }
res.status(400).json({ errors: errors.array() });
return; const networkId = req.params.id;
}
if (!req.user) {
const networkId = req.params.id; res.status(401).json({ message: 'Not authorized' });
return;
if (!req.user) { }
res.status(401).json({ message: 'Not authorized' });
return; const network = await Network.findById(networkId);
}
if (!network) {
const network = await Network.findById(networkId); res.status(404).json({ message: 'Network not found' });
return;
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' });
// Check if user is owner return;
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;
const { name, description, isPublic } = req.body; network.description = description !== undefined ? description : network.description;
network.isPublic = isPublic !== undefined ? isPublic : network.isPublic;
network.name = name || network.name;
network.description = description !== undefined ? description : network.description; await network.save();
network.isPublic = isPublic !== undefined ? isPublic : network.isPublic;
res.json({ success: true, data: network });
await network.save(); } catch (error) {
console.error('Update network error:', error);
res.json({ success: true, data: network }); res.status(500).json({ message: 'Server error' });
} 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 {
// Delete a network const networkId = req.params.id;
export const deleteNetwork = async (req: UserRequest, res: Response): Promise<void> => {
try { if (!req.user) {
const networkId = req.params.id; res.status(401).json({ message: 'Not authorized' });
return;
if (!req.user) { }
res.status(401).json({ message: 'Not authorized' });
return; const network = await Network.findById(networkId);
}
if (!network) {
const network = await Network.findById(networkId); res.status(404).json({ message: 'Network not found' });
return;
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' });
// Check if user is owner return;
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' });
await network.deleteOne(); // Changed from remove() to deleteOne() } catch (error) {
console.error('Delete network error:', error);
res.json({ success: true, message: 'Network deleted successfully' }); res.status(500).json({ message: 'Server error' });
} 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 { Response } from 'express';
import Person from '../models/person.model'; import Person from '../models/person.model';
import Relationship from '../models/relationship.model'; import Relationship from '../models/relationship.model';
import { UserRequest } from '../types/express'; import { UserRequest } from '../types/express';
import { validationResult } from 'express-validator'; import { validationResult } from 'express-validator';
import mongoose from 'mongoose'; import mongoose from 'mongoose';
// Get all people in a network // Get all people in a network
export const getPeople = async (req: UserRequest, res: Response): Promise<void> => { export const getPeople = async (req: UserRequest, res: Response): Promise<void> => {
try { try {
const networkId = req.params.networkId; const networkId = req.params.networkId;
if (!req.user || !req.network) { if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' }); res.status(401).json({ message: 'Not authorized' });
return; return;
} }
const people = await Person.find({ network: networkId }); const people = await Person.find({ network: networkId });
res.json({ success: true, data: people }); res.json({ success: true, data: people });
} catch (error) { } catch (error) {
console.error('Get people error:', error); console.error('Get people error:', error);
res.status(500).json({ message: 'Server error' }); res.status(500).json({ message: 'Server error' });
} }
}; };
// Add a person to the network // Add a person to the network
export const addPerson = async (req: UserRequest, res: Response): Promise<void> => { export const addPerson = async (req: UserRequest, res: Response): Promise<void> => {
try { try {
// Validate request // Validate request
const errors = validationResult(req); const errors = validationResult(req);
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() }); res.status(400).json({ errors: errors.array() });
return; return;
} }
const networkId = req.params.networkId; const networkId = req.params.networkId;
if (!req.user || !req.network) { if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' }); res.status(401).json({ message: 'Not authorized' });
return; return;
} }
// Check if user is the owner (only owners can add people) // Check if user is the owner (only owners can add people)
if (req.network.owner.toString() !== req.user._id.toString()) { if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can add people' }); res.status(403).json({ message: 'Only the network owner can add people' });
return; return;
} }
const { firstName, lastName, birthday, position } = req.body; const { firstName, lastName, birthday, position } = req.body;
// Check if person already exists in this network // Check if person already exists in this network
const existingPerson = await Person.findOne({ const existingPerson = await Person.findOne({
firstName, firstName,
lastName, lastName,
network: networkId, network: networkId,
}); });
if (existingPerson) { if (existingPerson) {
res.status(400).json({ message: 'This person already exists in the network' }); res.status(400).json({ message: 'This person already exists in the network' });
return; return;
} }
const person = new Person({ const person = new Person({
firstName, firstName,
lastName, lastName,
birthday: birthday || undefined, birthday: birthday || undefined,
network: networkId, network: networkId,
position: position || { x: 100 + Math.random() * 500, y: 100 + Math.random() * 400 }, position: position || { x: 100 + Math.random() * 500, y: 100 + Math.random() * 400 },
}); });
await person.save(); await person.save();
res.status(201).json({ success: true, data: person }); res.status(201).json({ success: true, data: person });
} catch (error) { } catch (error) {
console.error('Add person error:', error); console.error('Add person error:', error);
res.status(500).json({ message: 'Server error' }); res.status(500).json({ message: 'Server error' });
} }
}; };
// Update a person // Update a person
export const updatePerson = async (req: UserRequest, res: Response): Promise<void> => { export const updatePerson = async (req: UserRequest, res: Response): Promise<void> => {
try { try {
// Validate request // Validate request
const errors = validationResult(req); const errors = validationResult(req);
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() }); res.status(400).json({ errors: errors.array() });
return; return;
} }
const networkId = req.params.networkId; const networkId = req.params.networkId;
const personId = req.params.id; const personId = req.params.id;
if (!req.user || !req.network) { if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' }); res.status(401).json({ message: 'Not authorized' });
return; return;
} }
// Check if user is the owner (only owners can update people) // Check if user is the owner (only owners can update people)
if (req.network.owner.toString() !== req.user._id.toString()) { if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can update people' }); res.status(403).json({ message: 'Only the network owner can update people' });
return; return;
} }
const person = await Person.findOne({ const person = await Person.findOne({
_id: personId, _id: personId,
network: networkId, network: networkId,
}); });
if (!person) { if (!person) {
res.status(404).json({ message: 'Person not found' }); res.status(404).json({ message: 'Person not found' });
return; return;
} }
const { firstName, lastName, birthday, position } = req.body; const { firstName, lastName, birthday, position } = req.body;
// Update person // Update person
if (firstName) person.firstName = firstName; if (firstName) person.firstName = firstName;
if (lastName) person.lastName = lastName; if (lastName) person.lastName = lastName;
if (birthday !== undefined) person.birthday = birthday || undefined; if (birthday !== undefined) person.birthday = birthday || undefined;
if (position) person.position = position; if (position) person.position = position;
await person.save(); await person.save();
res.json({ success: true, data: person }); res.json({ success: true, data: person });
} catch (error) { } catch (error) {
console.error('Update person error:', error); console.error('Update person error:', error);
res.status(500).json({ message: 'Server error' }); res.status(500).json({ message: 'Server error' });
} }
}; };
// Remove a person from the network // Remove a person from the network
export const removePerson = async (req: UserRequest, res: Response): Promise<void> => { export const removePerson = async (req: UserRequest, res: Response): Promise<void> => {
try { try {
const networkId = req.params.networkId; const networkId = req.params.networkId;
const personId = req.params.id; const personId = req.params.id;
if (!req.user || !req.network) { if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' }); res.status(401).json({ message: 'Not authorized' });
return; return;
} }
// Check if user is the owner (only owners can remove people) // Check if user is the owner (only owners can remove people)
if (req.network.owner.toString() !== req.user._id.toString()) { if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can remove people' }); res.status(403).json({ message: 'Only the network owner can remove people' });
return; return;
} }
const person = await Person.findOne({ const person = await Person.findOne({
_id: personId, _id: personId,
network: networkId, network: networkId,
}); });
if (!person) { if (!person) {
res.status(404).json({ message: 'Person not found' }); res.status(404).json({ message: 'Person not found' });
return; return;
} }
// Remove all relationships involving this person // Remove all relationships involving this person
await Relationship.deleteMany({ await Relationship.deleteMany({
network: networkId, network: networkId,
$or: [{ source: personId }, { target: personId }], $or: [{ source: personId }, { target: personId }],
}); });
// Remove the person // Remove the person
await person.deleteOne(); // Changed from remove() to deleteOne() await person.deleteOne(); // Changed from remove() to deleteOne()
res.json({ success: true, message: 'Person and associated relationships removed successfully' }); res.json({
} catch (error) { success: true,
console.error('Remove person error:', error); message: 'Person and associated relationships removed successfully',
res.status(500).json({ message: 'Server error' }); });
} } 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 { Response } from 'express';
import Relationship from '../models/relationship.model'; import Relationship from '../models/relationship.model';
import Person from '../models/person.model'; import Person from '../models/person.model';
import { UserRequest } from '../types/express'; import { UserRequest } from '../types/express';
import { validationResult } from 'express-validator'; import { validationResult } from 'express-validator';
// Get all relationships in a network // Get all relationships in a network
export const getRelationships = async (req: UserRequest, res: Response): Promise<void> => { export const getRelationships = async (req: UserRequest, res: Response): Promise<void> => {
try { try {
const networkId = req.params.networkId; const networkId = req.params.networkId;
if (!req.user || !req.network) { if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' }); res.status(401).json({ message: 'Not authorized' });
return; return;
} }
const relationships = await Relationship.find({ network: networkId }); const relationships = await Relationship.find({ network: networkId });
res.json({ success: true, data: relationships }); res.json({ success: true, data: relationships });
} catch (error) { } catch (error) {
console.error('Get relationships error:', error); console.error('Get relationships error:', error);
res.status(500).json({ message: 'Server error' }); res.status(500).json({ message: 'Server error' });
} }
}; };
// Add a relationship to the network // Add a relationship to the network
export const addRelationship = async (req: UserRequest, res: Response): Promise<void> => { export const addRelationship = async (req: UserRequest, res: Response): Promise<void> => {
try { try {
// Validate request // Validate request
const errors = validationResult(req); const errors = validationResult(req);
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() }); res.status(400).json({ errors: errors.array() });
return; return;
} }
const networkId = req.params.networkId; const networkId = req.params.networkId;
if (!req.user || !req.network) { if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' }); res.status(401).json({ message: 'Not authorized' });
return; return;
} }
// Check if user is the owner (only owners can add relationships) // Check if user is the owner (only owners can add relationships)
if (req.network.owner.toString() !== req.user._id.toString()) { if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can add relationships' }); res.status(403).json({ message: 'Only the network owner can add relationships' });
return; return;
} }
const { source, target, type, customType } = req.body; const { source, target, type, customType } = req.body;
// Check if source and target exist and belong to the network // Check if source and target exist and belong to the network
const sourcePerson = await Person.findOne({ const sourcePerson = await Person.findOne({
_id: source, _id: source,
network: networkId, network: networkId,
}); });
const targetPerson = await Person.findOne({ const targetPerson = await Person.findOne({
_id: target, _id: target,
network: networkId, network: networkId,
}); });
if (!sourcePerson || !targetPerson) { if (!sourcePerson || !targetPerson) {
res.status(400).json({ message: 'Source or target person not found in this network' }); res.status(400).json({ message: 'Source or target person not found in this network' });
return; return;
} }
// Check if relationship already exists // Check if relationship already exists
const existingRelationship = await Relationship.findOne({ const existingRelationship = await Relationship.findOne({
$or: [ $or: [
{ source, target, network: networkId }, { source, target, network: networkId },
{ source: target, target: source, network: networkId }, { source: target, target: source, network: networkId },
], ],
}); });
if (existingRelationship) { if (existingRelationship) {
res.status(400).json({ message: 'A relationship already exists between these people' }); res.status(400).json({ message: 'A relationship already exists between these people' });
return; return;
} }
const relationship = new Relationship({ const relationship = new Relationship({
source, source,
target, target,
type, type,
customType: type === 'custom' ? customType : undefined, customType: type === 'custom' ? customType : undefined,
network: networkId, network: networkId,
}); });
await relationship.save(); await relationship.save();
res.status(201).json({ success: true, data: relationship }); res.status(201).json({ success: true, data: relationship });
} catch (error) { } catch (error) {
console.error('Add relationship error:', error); console.error('Add relationship error:', error);
res.status(500).json({ message: 'Server error' }); res.status(500).json({ message: 'Server error' });
} }
}; };
// Update a relationship // Update a relationship
export const updateRelationship = async (req: UserRequest, res: Response): Promise<void> => { export const updateRelationship = async (req: UserRequest, res: Response): Promise<void> => {
try { try {
// Validate request // Validate request
const errors = validationResult(req); const errors = validationResult(req);
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
res.status(400).json({ errors: errors.array() }); res.status(400).json({ errors: errors.array() });
return; return;
} }
const networkId = req.params.networkId; const networkId = req.params.networkId;
const relationshipId = req.params.id; const relationshipId = req.params.id;
if (!req.user || !req.network) { if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' }); res.status(401).json({ message: 'Not authorized' });
return; return;
} }
// Check if user is the owner (only owners can update relationships) // Check if user is the owner (only owners can update relationships)
if (req.network.owner.toString() !== req.user._id.toString()) { if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can update relationships' }); res.status(403).json({ message: 'Only the network owner can update relationships' });
return; return;
} }
const relationship = await Relationship.findOne({ const relationship = await Relationship.findOne({
_id: relationshipId, _id: relationshipId,
network: networkId, network: networkId,
}); });
if (!relationship) { if (!relationship) {
res.status(404).json({ message: 'Relationship not found' }); res.status(404).json({ message: 'Relationship not found' });
return; return;
} }
const { type, customType } = req.body; const { type, customType } = req.body;
// Update relationship // Update relationship
if (type) relationship.type = type; if (type) relationship.type = type;
if (type === 'custom' && customType) relationship.customType = customType; if (type === 'custom' && customType) relationship.customType = customType;
await relationship.save(); await relationship.save();
res.json({ success: true, data: relationship }); res.json({ success: true, data: relationship });
} catch (error) { } catch (error) {
console.error('Update relationship error:', error); console.error('Update relationship error:', error);
res.status(500).json({ message: 'Server error' }); res.status(500).json({ message: 'Server error' });
} }
}; };
// Remove a relationship // Remove a relationship
export const removeRelationship = async (req: UserRequest, res: Response): Promise<void> => { export const removeRelationship = async (req: UserRequest, res: Response): Promise<void> => {
try { try {
const networkId = req.params.networkId; const networkId = req.params.networkId;
const relationshipId = req.params.id; const relationshipId = req.params.id;
if (!req.user || !req.network) { if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' }); res.status(401).json({ message: 'Not authorized' });
return; return;
} }
// Check if user is the owner (only owners can remove relationships) // Check if user is the owner (only owners can remove relationships)
if (req.network.owner.toString() !== req.user._id.toString()) { if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can remove relationships' }); res.status(403).json({ message: 'Only the network owner can remove relationships' });
return; return;
} }
const relationship = await Relationship.findOne({ const relationship = await Relationship.findOne({
_id: relationshipId, _id: relationshipId,
network: networkId, network: networkId,
}); });
if (!relationship) { if (!relationship) {
res.status(404).json({ message: 'Relationship not found' }); res.status(404).json({ message: 'Relationship not found' });
return; return;
} }
await relationship.deleteOne(); // Changed from remove() to deleteOne() await relationship.deleteOne(); // Changed from remove() to deleteOne()
res.json({ success: true, message: 'Relationship removed successfully' }); res.json({ success: true, message: 'Relationship removed successfully' });
} catch (error) { } catch (error) {
console.error('Remove relationship error:', error); console.error('Remove relationship error:', error);
res.status(500).json({ message: 'Server error' }); res.status(500).json({ message: 'Server error' });
} }
}; };

View File

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

View File

@ -1,33 +1,37 @@
import { Response, NextFunction } from 'express'; import { Response, NextFunction } from 'express';
import Network from '../models/network.model'; import Network from '../models/network.model';
import { UserRequest } from '../types/express'; import { UserRequest } from '../types/express';
export const checkNetworkAccess = async (req: UserRequest, res: Response, next: NextFunction): Promise<void> => { export const checkNetworkAccess = async (
try { req: UserRequest,
const networkId = req.params.networkId; res: Response,
next: NextFunction
if (!networkId) { ): Promise<void> => {
res.status(400).json({ message: 'Network ID is required' }); try {
return; const networkId = req.params.networkId;
}
if (!networkId) {
const network = await Network.findById(networkId); res.status(400).json({ message: 'Network ID is required' });
return;
if (!network) { }
res.status(404).json({ message: 'Network not found' });
return; const network = await Network.findById(networkId);
}
if (!network) {
// Check if user is the owner or the network is public res.status(404).json({ message: 'Network not found' });
if (network.owner.toString() !== req.user?._id.toString() && !network.isPublic) { return;
res.status(403).json({ message: 'You do not have permission to access this network' }); }
return;
} // Check if user is the owner or the network is public
if (network.owner.toString() !== req.user?._id.toString() && !network.isPublic) {
// Add network to the request res.status(403).json({ message: 'You do not have permission to access this network' });
req.network = network; return;
next(); }
} catch (error) {
res.status(500).json({ message: 'Server error' }); // 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'; import mongoose, { Document, Schema } from 'mongoose';
export interface INetwork extends Document { export interface INetwork extends Document {
_id: string; _id: string;
name: string; name: string;
description?: string; description?: string;
owner: mongoose.Types.ObjectId; owner: mongoose.Types.ObjectId;
isPublic: boolean; isPublic: boolean;
} }
const NetworkSchema = new Schema( const NetworkSchema = new Schema(
{ {
name: { name: {
type: String, type: String,
required: [true, 'Network name is required'], required: [true, 'Network name is required'],
trim: true, trim: true,
}, },
description: { description: {
type: String, type: String,
trim: true, trim: true,
}, },
owner: { owner: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'User', ref: 'User',
required: true, required: true,
}, },
isPublic: { isPublic: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
}, },
{ timestamps: true } { timestamps: true }
); );
export default mongoose.model<INetwork>('Network', NetworkSchema); export default mongoose.model<INetwork>('Network', NetworkSchema);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,56 +1,62 @@
import express from 'express'; import express from 'express';
import { check } from 'express-validator'; import { check } from 'express-validator';
import * as relationshipController from '../controllers/relationship.controller'; import * as relationshipController from '../controllers/relationship.controller';
import { auth } from '../middleware/auth.middleware'; import { auth } from '../middleware/auth.middleware';
import { checkNetworkAccess } from '../middleware/network-access.middleware'; import { checkNetworkAccess } from '../middleware/network-access.middleware';
const router = express.Router(); const router = express.Router();
// All routes require authentication and network access check // All routes require authentication and network access check
router.use('/:networkId/relationships', auth, checkNetworkAccess); router.use('/:networkId/relationships', auth, checkNetworkAccess);
// @route GET /api/networks/:networkId/relationships // @route GET /api/networks/:networkId/relationships
// @desc Get all relationships in a network // @desc Get all relationships in a network
// @access Private // @access Private
router.get('/:networkId/relationships', relationshipController.getRelationships); router.get('/:networkId/relationships', relationshipController.getRelationships);
// @route POST /api/networks/:networkId/relationships // @route POST /api/networks/:networkId/relationships
// @desc Add a relationship to the network // @desc Add a relationship to the network
// @access Private // @access Private
router.post( router.post(
'/:networkId/relationships', '/:networkId/relationships',
[ [
check('source', 'Source person ID is required').not().isEmpty().isMongoId(), check('source', 'Source person ID is required').not().isEmpty().isMongoId(),
check('target', 'Target person ID is required').not().isEmpty().isMongoId(), check('target', 'Target person ID is required').not().isEmpty().isMongoId(),
check('type', 'Relationship type is required').isIn(['freund', 'partner', 'familie', 'arbeitskolleg', 'custom']), check('type', 'Relationship type is required').isIn([
check('customType', 'Custom type is required when type is custom') 'freund',
.if(check('type').equals('custom')) 'partner',
.not() 'familie',
.isEmpty(), 'arbeitskolleg',
], 'custom',
relationshipController.addRelationship ]),
); check('customType', 'Custom type is required when type is custom')
.if(check('type').equals('custom'))
// @route PUT /api/networks/:networkId/relationships/:id .not()
// @desc Update a relationship .isEmpty(),
// @access Private ],
router.put( relationshipController.addRelationship
'/:networkId/relationships/:id', );
[
check('type', 'Relationship type must be valid if provided') // @route PUT /api/networks/:networkId/relationships/:id
.optional() // @desc Update a relationship
.isIn(['freund', 'partner', 'familie', 'arbeitskolleg', 'custom']), // @access Private
check('customType', 'Custom type is required when type is custom') router.put(
.if(check('type').equals('custom')) '/:networkId/relationships/:id',
.not() [
.isEmpty(), check('type', 'Relationship type must be valid if provided')
], .optional()
relationshipController.updateRelationship .isIn(['freund', 'partner', 'familie', 'arbeitskolleg', 'custom']),
); check('customType', 'Custom type is required when type is custom')
.if(check('type').equals('custom'))
// @route DELETE /api/networks/:networkId/relationships/:id .not()
// @desc Remove a relationship .isEmpty(),
// @access Private ],
router.delete('/:networkId/relationships/:id', relationshipController.removeRelationship); relationshipController.updateRelationship
);
export default router;
// @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 app from './app';
import connectDB from './config/db'; import connectDB from './config/db';
import dotenv from 'dotenv'; import dotenv from 'dotenv';
dotenv.config(); dotenv.config();
const PORT = process.env.PORT || 5000; const PORT = process.env.PORT || 5000;
// Connect to MongoDB // Connect to MongoDB
connectDB(); connectDB();
// Start server // Start server
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`); console.log(`Server running on port ${PORT}`);
}); });

View File

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

2316
yarn.lock

File diff suppressed because it is too large Load Diff