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

@ -10,58 +10,58 @@ 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

@ -11,50 +11,50 @@ 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

@ -8,57 +8,65 @@ 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
);
return response.data.data;
}; };
// Get a specific network // Get a specific network
export const getNetwork = async (id: string): Promise<Network> => { export const getNetwork = async (id: string): Promise<Network> => {
const response = await axios.get<{ success: boolean; data: Network }>(`${API_URL}/networks/${id}`); const response = await axios.get<{ success: boolean; data: Network }>(
return response.data.data; `${API_URL}/networks/${id}`
);
return response.data.data;
}; };
// Update a network // Update a network
export const updateNetwork = async (id: string, data: UpdateNetworkData): Promise<Network> => { export const updateNetwork = async (id: string, data: UpdateNetworkData): Promise<Network> => {
const response = await axios.put<{ success: boolean; data: Network }>(`${API_URL}/networks/${id}`, data); const response = await axios.put<{ success: boolean; data: Network }>(
return response.data.data; `${API_URL}/networks/${id}`,
data
);
return response.data.data;
}; };
// Delete a network // Delete a network
export const deleteNetwork = async (id: string): Promise<void> => { export const deleteNetwork = async (id: string): Promise<void> => {
await axios.delete(`${API_URL}/networks/${id}`); await axios.delete(`${API_URL}/networks/${id}`);
}; };

View File

@ -8,70 +8,70 @@ 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

@ -8,62 +8,65 @@ 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

@ -3,87 +3,87 @@ 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

@ -3,129 +3,129 @@ 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 className="space-x-4">
<Link to="/login" className="hover:underline">
Login
</Link>
<Link
to="/register"
className="bg-white text-blue-600 hover:bg-gray-100 font-bold py-1 px-3 rounded"
>
Register
</Link>
</div>
)}
</nav>
</div> </div>
</header> ) : (
); <div className="space-x-4">
<Link to="/login" className="hover:underline">
Login
</Link>
<Link
to="/register"
className="bg-white text-blue-600 hover:bg-gray-100 font-bold py-1 px-3 rounded"
>
Register
</Link>
</div>
)}
</nav>
</div>
</header>
);
}; };
export default Header; export default Header;

View File

@ -4,271 +4,271 @@ 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);
try {
const network = await createNetwork({
name: newNetworkName.trim(),
description: newNetworkDescription.trim() || undefined,
isPublic
});
// Reset form
setNewNetworkName('');
setNewNetworkDescription('');
setIsPublic(false);
setShowCreateForm(false);
// Navigate to the new network
navigate(`/networks/${network._id}`);
} catch (err: any) {
setFormError(err.response?.data?.message || 'Failed to create network');
} finally {
setCreateLoading(false);
}
};
const handleDeleteNetwork = async (id: string) => {
if (window.confirm('Are you sure you want to delete this network? This action cannot be undone.')) {
try {
await deleteNetwork(id);
} catch (err: any) {
alert(err.response?.data?.message || 'Failed to delete network');
}
}
};
if (loading) {
return <div className="flex justify-center p-8">Loading networks...</div>;
} }
return ( setCreateLoading(true);
<div className="container mx-auto p-6">
<div className="flex justify-between items-center mb-6"> try {
<h1 className="text-2xl font-bold">My Networks</h1> const network = await createNetwork({
<button name: newNetworkName.trim(),
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" description: newNetworkDescription.trim() || undefined,
onClick={() => setShowCreateForm(!showCreateForm)} isPublic,
> });
{showCreateForm ? 'Cancel' : 'Create New Network'}
</button> // Reset form
setNewNetworkName('');
setNewNetworkDescription('');
setIsPublic(false);
setShowCreateForm(false);
// Navigate to the new network
navigate(`/networks/${network._id}`);
} catch (err: any) {
setFormError(err.response?.data?.message || 'Failed to create network');
} finally {
setCreateLoading(false);
}
};
const handleDeleteNetwork = async (id: string) => {
if (
window.confirm('Are you sure you want to delete this network? This action cannot be undone.')
) {
try {
await deleteNetwork(id);
} catch (err: any) {
alert(err.response?.data?.message || 'Failed to delete network');
}
}
};
if (loading) {
return <div className="flex justify-center p-8">Loading networks...</div>;
}
return (
<div className="container mx-auto p-6">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold">My Networks</h1>
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={() => setShowCreateForm(!showCreateForm)}
>
{showCreateForm ? 'Cancel' : 'Create New Network'}
</button>
</div>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
{/* Create Network Form */}
{showCreateForm && (
<div className="bg-gray-100 p-4 rounded-lg mb-6">
<h2 className="text-xl font-semibold mb-4">Create New Network</h2>
{formError && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{formError}
</div>
)}
<form onSubmit={handleCreateNetwork}>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="name">
Network Name *
</label>
<input
id="name"
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={newNetworkName}
onChange={e => setNewNetworkName(e.target.value)}
required
/>
</div> </div>
{error && ( <div className="mb-4">
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4"> <label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="description">
{error} Description (Optional)
</div> </label>
)} <textarea
id="description"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={newNetworkDescription}
onChange={e => setNewNetworkDescription(e.target.value)}
rows={3}
/>
</div>
{/* Create Network Form */} <div className="mb-4">
{showCreateForm && ( <label className="flex items-center">
<div className="bg-gray-100 p-4 rounded-lg mb-6"> <input
<h2 className="text-xl font-semibold mb-4">Create New Network</h2> type="checkbox"
className="mr-2"
checked={isPublic}
onChange={e => setIsPublic(e.target.checked)}
/>
<span className="text-gray-700 text-sm font-bold">Make this network public</span>
</label>
</div>
{formError && ( <button
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4"> type="submit"
{formError} className="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded"
</div> disabled={createLoading}
)} >
{createLoading ? 'Creating...' : 'Create Network'}
<form onSubmit={handleCreateNetwork}> </button>
<div className="mb-4"> </form>
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="name">
Network Name *
</label>
<input
id="name"
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={newNetworkName}
onChange={(e) => setNewNetworkName(e.target.value)}
required
/>
</div>
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="description">
Description (Optional)
</label>
<textarea
id="description"
className="w-full px-3 py-2 border border-gray-300 rounded"
value={newNetworkDescription}
onChange={(e) => setNewNetworkDescription(e.target.value)}
rows={3}
/>
</div>
<div className="mb-4">
<label className="flex items-center">
<input
type="checkbox"
className="mr-2"
checked={isPublic}
onChange={(e) => setIsPublic(e.target.checked)}
/>
<span className="text-gray-700 text-sm font-bold">Make this network public</span>
</label>
</div>
<button
type="submit"
className="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded"
disabled={createLoading}
>
{createLoading ? 'Creating...' : 'Create Network'}
</button>
</form>
</div>
)}
{/* Networks List */}
{networks.length === 0 ? (
<div className="bg-white p-8 rounded-lg text-center">
<p className="text-gray-600 mb-4">You don't have any networks yet.</p>
{!showCreateForm && (
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={() => setShowCreateForm(true)}
>
Create Your First Network
</button>
)}
</div>
) : (
<>
{/* My Networks Section */}
<div className="mb-8">
<h2 className="text-xl font-semibold mb-4">My Networks</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{networks
.filter(network => {
if (!user) return false;
const ownerId = typeof network.owner === 'string'
? network.owner
: network.owner._id;
return ownerId === user.id;
})
.map((network) => (
<div key={network._id} className="bg-white rounded-lg shadow-md overflow-hidden">
<div className="p-4">
<h2 className="text-xl font-bold mb-2">{network.name}</h2>
{network.description && (
<p className="text-gray-600 mb-4">{network.description}</p>
)}
<div className="flex items-center mb-4">
<span className={`px-2 py-1 rounded-full text-xs ${network.isPublic ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}>
{network.isPublic ? 'Public' : 'Private'}
</span>
<span className="text-xs text-gray-500 ml-2">
Created: {new Date(network.createdAt).toLocaleDateString()}
</span>
</div>
<div className="flex space-x-2">
<button
className="flex-1 bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded"
onClick={() => navigate(`/networks/${network._id}`)}
>
View
</button>
<button
className="flex-1 bg-red-500 hover:bg-red-700 text-white py-2 px-4 rounded"
onClick={() => handleDeleteNetwork(network._id)}
>
Delete
</button>
</div>
</div>
</div>
))}
</div>
{networks.filter(network => {
if (!user) return false;
const ownerId = typeof network.owner === 'string'
? network.owner
: network.owner._id;
return ownerId === user.id;
}).length === 0 && (
<p className="text-gray-600 mb-4">You haven't created any networks yet.</p>
)}
</div>
{/* Public Networks Section */}
{networks.some(network => {
if (!user) return false;
const ownerId = typeof network.owner === 'string'
? network.owner
: network.owner._id;
return ownerId !== user.id;
}) && (
<div>
<h2 className="text-xl font-semibold mb-4">Public Networks From Others</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{networks
.filter(network => {
if (!user) return false;
const ownerId = typeof network.owner === 'string'
? network.owner
: network.owner._id;
return ownerId !== user.id;
})
.map((network) => (
<div key={network._id} className="bg-white rounded-lg shadow-md overflow-hidden border-l-4 border-green-500">
<div className="p-4">
<h2 className="text-xl font-bold mb-2">{network.name}</h2>
{network.description && (
<p className="text-gray-600 mb-4">{network.description}</p>
)}
<div className="flex items-center mb-4">
<span className="px-2 py-1 rounded-full text-xs bg-green-100 text-green-800">
Public
</span>
<span className="text-xs text-gray-500 ml-2">
Created: {new Date(network.createdAt).toLocaleDateString()}
</span>
<span className="text-xs text-gray-500 ml-2">
By: {typeof network.owner === 'string'
? 'Unknown'
: network.owner.username}
</span>
</div>
<div className="flex space-x-2">
<button
className="w-full bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded"
onClick={() => navigate(`/networks/${network._id}`)}
>
View
</button>
</div>
</div>
</div>
))}
</div>
</div>
)}
</>
)}
</div> </div>
); )}
{/* Networks List */}
{networks.length === 0 ? (
<div className="bg-white p-8 rounded-lg text-center">
<p className="text-gray-600 mb-4">You don't have any networks yet.</p>
{!showCreateForm && (
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
onClick={() => setShowCreateForm(true)}
>
Create Your First Network
</button>
)}
</div>
) : (
<>
{/* My Networks Section */}
<div className="mb-8">
<h2 className="text-xl font-semibold mb-4">My Networks</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{networks
.filter(network => {
if (!user) return false;
const ownerId =
typeof network.owner === 'string' ? network.owner : network.owner._id;
return ownerId === user.id;
})
.map(network => (
<div key={network._id} className="bg-white rounded-lg shadow-md overflow-hidden">
<div className="p-4">
<h2 className="text-xl font-bold mb-2">{network.name}</h2>
{network.description && (
<p className="text-gray-600 mb-4">{network.description}</p>
)}
<div className="flex items-center mb-4">
<span
className={`px-2 py-1 rounded-full text-xs ${network.isPublic ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}
>
{network.isPublic ? 'Public' : 'Private'}
</span>
<span className="text-xs text-gray-500 ml-2">
Created: {new Date(network.createdAt).toLocaleDateString()}
</span>
</div>
<div className="flex space-x-2">
<button
className="flex-1 bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded"
onClick={() => navigate(`/networks/${network._id}`)}
>
View
</button>
<button
className="flex-1 bg-red-500 hover:bg-red-700 text-white py-2 px-4 rounded"
onClick={() => handleDeleteNetwork(network._id)}
>
Delete
</button>
</div>
</div>
</div>
))}
</div>
{networks.filter(network => {
if (!user) return false;
const ownerId = typeof network.owner === 'string' ? network.owner : network.owner._id;
return ownerId === user.id;
}).length === 0 && (
<p className="text-gray-600 mb-4">You haven't created any networks yet.</p>
)}
</div>
{/* Public Networks Section */}
{networks.some(network => {
if (!user) return false;
const ownerId = typeof network.owner === 'string' ? network.owner : network.owner._id;
return ownerId !== user.id;
}) && (
<div>
<h2 className="text-xl font-semibold mb-4">Public Networks From Others</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{networks
.filter(network => {
if (!user) return false;
const ownerId =
typeof network.owner === 'string' ? network.owner : network.owner._id;
return ownerId !== user.id;
})
.map(network => (
<div
key={network._id}
className="bg-white rounded-lg shadow-md overflow-hidden border-l-4 border-green-500"
>
<div className="p-4">
<h2 className="text-xl font-bold mb-2">{network.name}</h2>
{network.description && (
<p className="text-gray-600 mb-4">{network.description}</p>
)}
<div className="flex items-center mb-4">
<span className="px-2 py-1 rounded-full text-xs bg-green-100 text-green-800">
Public
</span>
<span className="text-xs text-gray-500 ml-2">
Created: {new Date(network.createdAt).toLocaleDateString()}
</span>
<span className="text-xs text-gray-500 ml-2">
By:{' '}
{typeof network.owner === 'string' ? 'Unknown' : network.owner.username}
</span>
</div>
<div className="flex space-x-2">
<button
className="w-full bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded"
onClick={() => navigate(`/networks/${network._id}`)}
>
View
</button>
</div>
</div>
</div>
))}
</div>
</div>
)}
</>
)}
</div>
);
}; };
export default NetworkList; 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,
getCurrentUser,
login as apiLogin,
register as apiRegister,
logout as apiLogout,
LoginData,
RegisterData,
} from '../api/auth';
interface AuthContextProps { interface AuthContextProps {
user: User | null; user: User | null;
loading: boolean; loading: boolean;
login: (data: LoginData) => Promise<void>; login: (data: LoginData) => Promise<void>;
register: (data: RegisterData) => Promise<void>; register: (data: RegisterData) => Promise<void>;
logout: () => Promise<void>; logout: () => Promise<void>;
} }
const AuthContext = createContext<AuthContextProps>({} as AuthContextProps); const AuthContext = createContext<AuthContextProps>({} as AuthContextProps);
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => { export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null); const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
const loadUser = async () => { const loadUser = async () => {
try { try {
const currentUser = await getCurrentUser(); const currentUser = await getCurrentUser();
setUser(currentUser); setUser(currentUser);
} catch (error) { } catch (error) {
console.error('Error loading user:', error); console.error('Error loading user:', error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
};
loadUser();
}, []);
const login = async (data: LoginData) => {
try {
const loggedInUser = await apiLogin(data);
setUser(loggedInUser);
} catch (error) {
console.error('Login error:', error);
throw error;
}
}; };
const register = async (data: RegisterData) => { loadUser();
try { }, []);
const newUser = await apiRegister(data);
setUser(newUser);
} catch (error) {
console.error('Registration error:', error);
throw error;
}
};
const logout = async () => { const login = async (data: LoginData) => {
try { try {
await apiLogout(); const loggedInUser = await apiLogin(data);
setUser(null); setUser(loggedInUser);
} catch (error) { } catch (error) {
console.error('Logout error:', error); console.error('Login error:', error);
throw error; throw error;
} }
}; };
return ( const register = async (data: RegisterData) => {
<AuthContext.Provider value={{ user, loading, login, register, logout }}> try {
{children} const newUser = await apiRegister(data);
</AuthContext.Provider> setUser(newUser);
); } catch (error) {
console.error('Registration error:', error);
throw error;
}
};
const logout = async () => {
try {
await apiLogout();
setUser(null);
} catch (error) {
console.error('Logout error:', error);
throw error;
}
};
return (
<AuthContext.Provider value={{ user, loading, login, register, logout }}>
{children}
</AuthContext.Provider>
);
}; };
export const useAuth = () => useContext(AuthContext); 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> => { const deleteNetwork = async (id: string): Promise<void> => {
try { try {
await apiDeleteNetwork(id); await apiDeleteNetwork(id);
setNetworks(networks.filter(network => network._id !== id)); setNetworks(networks.filter(network => network._id !== id));
} catch (err: any) { } catch (err: any) {
setError(err.message || 'Failed to delete network'); setError(err.message || 'Failed to delete network');
throw err; throw err;
} }
}; };
const refreshNetworks = async (): Promise<void> => { const refreshNetworks = async (): Promise<void> => {
await loadNetworks(); await loadNetworks();
}; };
return ( return (
<NetworkContext.Provider <NetworkContext.Provider
value={{ value={{
networks, networks,
loading, loading,
error, error,
createNetwork, createNetwork,
updateNetwork, updateNetwork,
deleteNetwork, deleteNetwork,
refreshNetworks refreshNetworks,
}} }}
> >
{children} {children}
</NetworkContext.Provider> </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,
getRelationships,
addRelationship,
updateRelationship,
removeRelationship,
} from '../api/relationships';
interface PersonNode extends Person { interface PersonNode extends Person {
// Additional properties needed for the visualization // Additional properties needed for the visualization
id: string; // Alias for _id to work with the visualization id: string; // Alias for _id to work with the visualization
} }
interface RelationshipEdge extends Relationship { interface RelationshipEdge extends Relationship {
// Additional properties needed for the visualization // Additional properties needed for the visualization
id: string; // Alias for _id to work with the visualization id: string; // Alias for _id to work with the visualization
} }
// Custom hook to manage friendship network data // Custom hook to manage friendship network data
export const useFriendshipNetwork = (networkId: string | null) => { export const useFriendshipNetwork = (networkId: string | null) => {
const [people, setPeople] = useState<PersonNode[]>([]); const [people, setPeople] = useState<PersonNode[]>([]);
const [relationships, setRelationships] = useState<RelationshipEdge[]>([]); const [relationships, setRelationships] = useState<RelationshipEdge[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
// Load network data // Load network data
const loadNetworkData = useCallback(async () => { const loadNetworkData = useCallback(async () => {
if (!networkId) { if (!networkId) {
setPeople([]); setPeople([]);
setRelationships([]); setRelationships([]);
setLoading(false); setLoading(false);
return; return;
} }
try { try {
setLoading(true); setLoading(true);
setError(null); setError(null);
// Fetch people and relationships in parallel // Fetch people and relationships in parallel
const [peopleData, relationshipsData] = await Promise.all([ const [peopleData, relationshipsData] = await Promise.all([
getPeople(networkId), getPeople(networkId),
getRelationships(networkId) getRelationships(networkId),
]); ]);
// Transform to add the id property needed by the visualization // Transform to add the id property needed by the visualization
const peopleNodes: PersonNode[] = peopleData.map(person => ({ const peopleNodes: PersonNode[] = peopleData.map(person => ({
...person, ...person,
id: person._id id: person._id,
})); }));
const relationshipEdges: RelationshipEdge[] = relationshipsData.map(rel => ({ const relationshipEdges: RelationshipEdge[] = relationshipsData.map(rel => ({
...rel, ...rel,
id: rel._id id: rel._id,
})); }));
setPeople(peopleNodes); setPeople(peopleNodes);
setRelationships(relationshipEdges); setRelationships(relationshipEdges);
} catch (err: any) { } catch (err: any) {
setError(err.message || 'Failed to load network data'); setError(err.message || 'Failed to load network data');
console.error('Error loading network data:', err); console.error('Error loading network data:', err);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [networkId]); }, [networkId]);
useEffect(() => { useEffect(() => {
loadNetworkData(); loadNetworkData();
}, [loadNetworkData]); }, [loadNetworkData]);
// Add a new person // Add a new person
const createPerson = async (personData: { const createPerson = async (personData: {
firstName: string; firstName: string;
lastName: string; lastName: string;
birthday?: string; birthday?: string;
position?: { x: number; y: number } position?: { x: number; y: number };
}): Promise<PersonNode> => { }): Promise<PersonNode> => {
if (!networkId) throw new Error('No network selected'); if (!networkId) throw new Error('No network selected');
try { try {
const newPerson = await addPerson(networkId, personData); const newPerson = await addPerson(networkId, personData);
const newPersonNode: PersonNode = { ...newPerson, id: newPerson._id }; const newPersonNode: PersonNode = { ...newPerson, id: newPerson._id };
setPeople([...people, newPersonNode]); setPeople([...people, newPersonNode]);
return newPersonNode; return newPersonNode;
} catch (err: any) { } catch (err: any) {
setError(err.message || 'Failed to create person'); setError(err.message || 'Failed to create person');
throw err; throw err;
} }
}; };
// Update a person // Update a person
const updatePersonData = async ( const updatePersonData = async (
personId: string, personId: string,
personData: { personData: {
firstName?: string; firstName?: string;
lastName?: string; lastName?: string;
birthday?: string | null; birthday?: string | null;
position?: { x: number; y: number } position?: { x: number; y: number };
} }
): Promise<PersonNode> => { ): Promise<PersonNode> => {
if (!networkId) throw new Error('No network selected'); if (!networkId) throw new Error('No network selected');
try { try {
const updatedPerson = await updatePerson(networkId, personId, personData); const updatedPerson = await updatePerson(networkId, personId, personData);
const updatedPersonNode: PersonNode = { ...updatedPerson, id: updatedPerson._id }; const updatedPersonNode: PersonNode = { ...updatedPerson, id: updatedPerson._id };
setPeople(people.map(person => setPeople(people.map(person => (person._id === personId ? updatedPersonNode : person)));
person._id === personId ? updatedPersonNode : person
));
return updatedPersonNode; return updatedPersonNode;
} catch (err: any) { } catch (err: any) {
setError(err.message || 'Failed to update person'); setError(err.message || 'Failed to update person');
throw err; throw err;
} }
}; };
// Remove a person // Remove a person
const deletePerson = async (personId: string): Promise<void> => { const deletePerson = async (personId: string): Promise<void> => {
if (!networkId) throw new Error('No network selected'); if (!networkId) throw new Error('No network selected');
try { try {
await removePerson(networkId, personId); await removePerson(networkId, personId);
// Remove the person // Remove the person
setPeople(people.filter(person => person._id !== personId)); setPeople(people.filter(person => person._id !== personId));
// Remove all relationships involving this person // Remove all relationships involving this person
setRelationships(relationships.filter( setRelationships(
rel => rel.source !== personId && rel.target !== personId relationships.filter(rel => rel.source !== personId && rel.target !== personId)
)); );
} catch (err: any) { } catch (err: any) {
setError(err.message || 'Failed to delete person'); setError(err.message || 'Failed to delete person');
throw err; throw err;
} }
}; };
// Create a relationship // Create a relationship
const createRelationship = async (relationshipData: { const createRelationship = async (relationshipData: {
source: string; source: string;
target: string; target: string;
type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom'; type: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string; customType?: string;
}): Promise<RelationshipEdge> => { }): Promise<RelationshipEdge> => {
if (!networkId) throw new Error('No network selected'); if (!networkId) throw new Error('No network selected');
try { try {
const newRelationship = await addRelationship(networkId, relationshipData); const newRelationship = await addRelationship(networkId, relationshipData);
const newRelationshipEdge: RelationshipEdge = { ...newRelationship, id: newRelationship._id }; const newRelationshipEdge: RelationshipEdge = { ...newRelationship, id: newRelationship._id };
setRelationships([...relationships, newRelationshipEdge]); setRelationships([...relationships, newRelationshipEdge]);
return newRelationshipEdge; return newRelationshipEdge;
} catch (err: any) { } catch (err: any) {
setError(err.message || 'Failed to create relationship'); setError(err.message || 'Failed to create relationship');
throw err; throw err;
} }
}; };
// Update a relationship // Update a relationship
const updateRelationshipData = async ( const updateRelationshipData = async (
relationshipId: string, relationshipId: string,
relationshipData: { relationshipData: {
type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom'; type?: 'freund' | 'partner' | 'familie' | 'arbeitskolleg' | 'custom';
customType?: string; customType?: string;
} }
): Promise<RelationshipEdge> => { ): Promise<RelationshipEdge> => {
if (!networkId) throw new Error('No network selected'); if (!networkId) throw new Error('No network selected');
try { try {
const updatedRelationship = await updateRelationship(networkId, relationshipId, relationshipData); const updatedRelationship = await updateRelationship(
const updatedRelationshipEdge: RelationshipEdge = { ...updatedRelationship, id: updatedRelationship._id }; networkId,
relationshipId,
relationshipData
);
const updatedRelationshipEdge: RelationshipEdge = {
...updatedRelationship,
id: updatedRelationship._id,
};
setRelationships(relationships.map(rel => setRelationships(
rel._id === relationshipId ? updatedRelationshipEdge : rel relationships.map(rel => (rel._id === relationshipId ? updatedRelationshipEdge : rel))
)); );
return updatedRelationshipEdge; return updatedRelationshipEdge;
} catch (err: any) { } catch (err: any) {
setError(err.message || 'Failed to update relationship'); setError(err.message || 'Failed to update relationship');
throw err; throw err;
} }
}; };
// Remove a relationship // Remove a relationship
const deleteRelationship = async (relationshipId: string): Promise<void> => { const deleteRelationship = async (relationshipId: string): Promise<void> => {
if (!networkId) throw new Error('No network selected'); if (!networkId) throw new Error('No network selected');
try { try {
await removeRelationship(networkId, relationshipId); await removeRelationship(networkId, relationshipId);
setRelationships(relationships.filter(rel => rel._id !== relationshipId)); setRelationships(relationships.filter(rel => rel._id !== relationshipId));
} catch (err: any) { } catch (err: any) {
setError(err.message || 'Failed to delete relationship'); setError(err.message || 'Failed to delete relationship');
throw err; throw err;
} }
}; };
// Refresh the network data // Refresh the network data
const refreshNetwork = async (): Promise<void> => { const refreshNetwork = async (): Promise<void> => {
await loadNetworkData(); await loadNetworkData();
}; };
return { return {
people, people,
relationships, relationships,
loading, loading,
error, error,
createPerson, createPerson,
updatePerson: updatePersonData, updatePerson: updatePersonData,
deletePerson, deletePerson,
createRelationship, createRelationship,
updateRelationship: updateRelationshipData, updateRelationship: updateRelationshipData,
deleteRelationship, deleteRelationship,
refreshNetwork refreshNetwork,
}; };
}; };

View File

@ -6,12 +6,12 @@ import App from './App';
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

@ -6,20 +6,21 @@ 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(cors({ app.use(
cors({
origin: process.env.CLIENT_URL || 'http://localhost:3000', origin: process.env.CLIENT_URL || 'http://localhost:3000',
credentials: true credentials: true,
})); })
);
// Routes // Routes
app.use('/api/auth', authRoutes); app.use('/api/auth', authRoutes);
@ -32,11 +33,10 @@ app.use('/api/networks', relationshipRoutes);
res.send('Friendship Network API is running'); 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

@ -6,13 +6,13 @@ 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

@ -11,156 +11,155 @@ 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)
{
res.status(403).json({errors: ["Registration is disabled"]});
return;
}
const { email, password, username } = req.body;
// Check if user already exists
let user = await User.findOne({ email });
if (user) {
res.status(400).json({ message: 'User already exists' });
return;
}
// Create new user
user = new User({
email,
password,
username,
});
// Save user to database
await user.save();
// Generate JWT token
const token = generateToken(user);
// Set token cookie
setTokenCookie(res, token);
// Send response
res.status(201).json({
success: true,
user: {
id: user._id,
email: user.email,
username: user.username,
},
});
} catch (error) {
console.error('Registration error:', error);
res.status(500).json({ message: 'Server error' });
} }
if (!process.env.ENABLE_REGISTRATION) {
res.status(403).json({ errors: ['Registration is disabled'] });
return;
}
const { email, password, username } = req.body;
// Check if user already exists
let user = await User.findOne({ email });
if (user) {
res.status(400).json({ message: 'User already exists' });
return;
}
// Create new user
user = new User({
email,
password,
username,
});
// Save user to database
await user.save();
// Generate JWT token
const token = generateToken(user);
// Set token cookie
setTokenCookie(res, token);
// Send response
res.status(201).json({
success: true,
user: {
id: user._id,
email: user.email,
username: user.username,
},
});
} catch (error) {
console.error('Registration error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };
// Login user // 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;
// Check if user exists
const user = await User.findOne({ email });
if (!user) {
res.status(400).json({ message: 'Invalid credentials' });
return;
}
// Check if password is correct
const isMatch = await user.comparePassword(password);
if (!isMatch) {
res.status(400).json({ message: 'Invalid credentials' });
return;
}
// Generate JWT token
const token = generateToken(user);
// Set token cookie
setTokenCookie(res, token);
// Send response
res.json({
success: true,
user: {
id: user._id,
email: user.email,
username: user.username,
},
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ message: 'Server error' });
} }
const { email, password } = req.body;
// Check if user exists
const user = await User.findOne({ email });
if (!user) {
res.status(400).json({ message: 'Invalid credentials' });
return;
}
// Check if password is correct
const isMatch = await user.comparePassword(password);
if (!isMatch) {
res.status(400).json({ message: 'Invalid credentials' });
return;
}
// Generate JWT token
const token = generateToken(user);
// Set token cookie
setTokenCookie(res, token);
// Send response
res.json({
success: true,
user: {
id: user._id,
email: user.email,
username: user.username,
},
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };
// Logout user // 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({
success: true,
user: {
id: user._id,
email: user.email,
username: user.username,
},
});
} catch (error) {
console.error('Get current user error:', error);
res.status(500).json({ message: 'Server error' });
} }
res.json({
success: true,
user: {
id: user._id,
email: user.email,
username: user.username,
},
});
} catch (error) {
console.error('Get current user error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };

View File

@ -5,165 +5,162 @@ 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:
// 1. Belong to the current user, OR
// 2. Are public networks (created by any user)
const networks = await Network.find({
$or: [
{ owner: req.user._id },
{ isPublic: true }
]
}).populate('owner', 'username _id'); // Populate owner field with username
res.json({ success: true, data: networks });
} catch (error) {
console.error('Get networks error:', error);
res.status(500).json({ message: 'Server error' });
} }
// Find networks that either:
// 1. Belong to the current user, OR
// 2. Are public networks (created by any user)
const networks = await Network.find({
$or: [{ owner: req.user._id }, { isPublic: true }],
}).populate('owner', 'username _id'); // Populate owner field with username
res.json({ success: true, data: networks });
} catch (error) {
console.error('Get networks error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };
// Create a new network // Create a new network
export const createNetwork = async (req: UserRequest, res: Response): Promise<void> => { export const createNetwork = 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;
}
if (!req.user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const { name, description, isPublic } = req.body;
const network = new Network({
name,
description,
owner: req.user._id,
isPublic: isPublic || false,
});
await network.save();
res.status(201).json({ success: true, data: network });
} catch (error) {
console.error('Create network error:', error);
res.status(500).json({ message: 'Server error' });
} }
if (!req.user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const { name, description, isPublic } = req.body;
const network = new Network({
name,
description,
owner: req.user._id,
isPublic: isPublic || false,
});
await network.save();
res.status(201).json({ success: true, data: network });
} catch (error) {
console.error('Create network error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };
// Get a specific network // Get a specific network
export const getNetwork = async (req: UserRequest, res: Response): Promise<void> => { export const getNetwork = async (req: UserRequest, res: Response): Promise<void> => {
try { try {
const networkId = req.params.id; const networkId = req.params.id;
if (!req.user) { if (!req.user) {
res.status(401).json({ message: 'Not authorized' }); res.status(401).json({ message: 'Not authorized' });
return; return;
}
const network = await Network.findById(networkId).populate('owner', 'username _id');
if (!network) {
res.status(404).json({ message: 'Network not found' });
return;
}
// Check if user is owner or network is public
if (network.owner._id.toString() !== req.user._id.toString() && !network.isPublic) {
res.status(403).json({ message: 'You do not have permission to access this network' });
return;
}
res.json({ success: true, data: network });
} catch (error) {
console.error('Get network error:', error);
res.status(500).json({ message: 'Server error' });
} }
const network = await Network.findById(networkId).populate('owner', 'username _id');
if (!network) {
res.status(404).json({ message: 'Network not found' });
return;
}
// Check if user is owner or network is public
if (network.owner._id.toString() !== req.user._id.toString() && !network.isPublic) {
res.status(403).json({ message: 'You do not have permission to access this network' });
return;
}
res.json({ success: true, data: network });
} catch (error) {
console.error('Get network error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };
// Update a network // Update a network
export const updateNetwork = async (req: UserRequest, res: Response): Promise<void> => { export const updateNetwork = 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.id;
if (!req.user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const network = await Network.findById(networkId);
if (!network) {
res.status(404).json({ message: 'Network not found' });
return;
}
// Check if user is owner
if (network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'You do not have permission to update this network' });
return;
}
const { name, description, isPublic } = req.body;
network.name = name || network.name;
network.description = description !== undefined ? description : network.description;
network.isPublic = isPublic !== undefined ? isPublic : network.isPublic;
await network.save();
res.json({ success: true, data: network });
} catch (error) {
console.error('Update network error:', error);
res.status(500).json({ message: 'Server error' });
} }
const networkId = req.params.id;
if (!req.user) {
res.status(401).json({ message: 'Not authorized' });
return;
}
const network = await Network.findById(networkId);
if (!network) {
res.status(404).json({ message: 'Network not found' });
return;
}
// Check if user is owner
if (network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'You do not have permission to update this network' });
return;
}
const { name, description, isPublic } = req.body;
network.name = name || network.name;
network.description = description !== undefined ? description : network.description;
network.isPublic = isPublic !== undefined ? isPublic : network.isPublic;
await network.save();
res.json({ success: true, data: network });
} catch (error) {
console.error('Update network error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };
// Delete a network // Delete a network
export const deleteNetwork = async (req: UserRequest, res: Response): Promise<void> => { export const deleteNetwork = async (req: UserRequest, res: Response): Promise<void> => {
try { try {
const networkId = req.params.id; const networkId = req.params.id;
if (!req.user) { if (!req.user) {
res.status(401).json({ message: 'Not authorized' }); res.status(401).json({ message: 'Not authorized' });
return; return;
}
const network = await Network.findById(networkId);
if (!network) {
res.status(404).json({ message: 'Network not found' });
return;
}
// Check if user is owner
if (network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'You do not have permission to delete this network' });
return;
}
await network.deleteOne(); // Changed from remove() to deleteOne()
res.json({ success: true, message: 'Network deleted successfully' });
} catch (error) {
console.error('Delete network error:', error);
res.status(500).json({ message: 'Server error' });
} }
const network = await Network.findById(networkId);
if (!network) {
res.status(404).json({ message: 'Network not found' });
return;
}
// Check if user is owner
if (network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'You do not have permission to delete this network' });
return;
}
await network.deleteOne(); // Changed from remove() to deleteOne()
res.json({ success: true, message: 'Network deleted successfully' });
} catch (error) {
console.error('Delete network error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };

View File

@ -7,168 +7,170 @@ 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 });
res.json({ success: true, data: people });
} catch (error) {
console.error('Get people error:', error);
res.status(500).json({ message: 'Server error' });
} }
const people = await Person.find({ network: networkId });
res.json({ success: true, data: people });
} catch (error) {
console.error('Get people error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };
// Add a person to the network // 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;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can add people)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can add people' });
return;
}
const { firstName, lastName, birthday, position } = req.body;
// Check if person already exists in this network
const existingPerson = await Person.findOne({
firstName,
lastName,
network: networkId,
});
if (existingPerson) {
res.status(400).json({ message: 'This person already exists in the network' });
return;
}
const person = new Person({
firstName,
lastName,
birthday: birthday || undefined,
network: networkId,
position: position || { x: 100 + Math.random() * 500, y: 100 + Math.random() * 400 },
});
await person.save();
res.status(201).json({ success: true, data: person });
} catch (error) {
console.error('Add person error:', error);
res.status(500).json({ message: 'Server error' });
} }
const networkId = req.params.networkId;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can add people)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can add people' });
return;
}
const { firstName, lastName, birthday, position } = req.body;
// Check if person already exists in this network
const existingPerson = await Person.findOne({
firstName,
lastName,
network: networkId,
});
if (existingPerson) {
res.status(400).json({ message: 'This person already exists in the network' });
return;
}
const person = new Person({
firstName,
lastName,
birthday: birthday || undefined,
network: networkId,
position: position || { x: 100 + Math.random() * 500, y: 100 + Math.random() * 400 },
});
await person.save();
res.status(201).json({ success: true, data: person });
} catch (error) {
console.error('Add person error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };
// Update a person // 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 personId = req.params.id;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can update people)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can update people' });
return;
}
const person = await Person.findOne({
_id: personId,
network: networkId,
});
if (!person) {
res.status(404).json({ message: 'Person not found' });
return;
}
const { firstName, lastName, birthday, position } = req.body;
// Update person
if (firstName) person.firstName = firstName;
if (lastName) person.lastName = lastName;
if (birthday !== undefined) person.birthday = birthday || undefined;
if (position) person.position = position;
await person.save();
res.json({ success: true, data: person });
} catch (error) {
console.error('Update person error:', error);
res.status(500).json({ message: 'Server error' });
} }
const networkId = req.params.networkId;
const personId = req.params.id;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can update people)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can update people' });
return;
}
const person = await Person.findOne({
_id: personId,
network: networkId,
});
if (!person) {
res.status(404).json({ message: 'Person not found' });
return;
}
const { firstName, lastName, birthday, position } = req.body;
// Update person
if (firstName) person.firstName = firstName;
if (lastName) person.lastName = lastName;
if (birthday !== undefined) person.birthday = birthday || undefined;
if (position) person.position = position;
await person.save();
res.json({ success: true, data: person });
} catch (error) {
console.error('Update person error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };
// Remove a person from the network // 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)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can remove people' });
return;
}
const person = await Person.findOne({
_id: personId,
network: networkId,
});
if (!person) {
res.status(404).json({ message: 'Person not found' });
return;
}
// Remove all relationships involving this person
await Relationship.deleteMany({
network: networkId,
$or: [{ source: personId }, { target: personId }],
});
// Remove the person
await person.deleteOne(); // Changed from remove() to deleteOne()
res.json({ success: true, message: 'Person and associated relationships removed successfully' });
} catch (error) {
console.error('Remove person error:', error);
res.status(500).json({ message: 'Server error' });
} }
};
// Check if user is the owner (only owners can remove people)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can remove people' });
return;
}
const person = await Person.findOne({
_id: personId,
network: networkId,
});
if (!person) {
res.status(404).json({ message: 'Person not found' });
return;
}
// Remove all relationships involving this person
await Relationship.deleteMany({
network: networkId,
$or: [{ source: personId }, { target: personId }],
});
// Remove the person
await person.deleteOne(); // Changed from remove() to deleteOne()
res.json({
success: true,
message: 'Person and associated relationships removed successfully',
});
} catch (error) {
console.error('Remove person error:', error);
res.status(500).json({ message: 'Server error' });
}
};

View File

@ -6,175 +6,175 @@ 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 });
res.json({ success: true, data: relationships });
} catch (error) {
console.error('Get relationships error:', error);
res.status(500).json({ message: 'Server error' });
} }
const relationships = await Relationship.find({ network: networkId });
res.json({ success: true, data: relationships });
} catch (error) {
console.error('Get relationships error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };
// Add a relationship to the network // 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;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can add relationships)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can add relationships' });
return;
}
const { source, target, type, customType } = req.body;
// Check if source and target exist and belong to the network
const sourcePerson = await Person.findOne({
_id: source,
network: networkId,
});
const targetPerson = await Person.findOne({
_id: target,
network: networkId,
});
if (!sourcePerson || !targetPerson) {
res.status(400).json({ message: 'Source or target person not found in this network' });
return;
}
// Check if relationship already exists
const existingRelationship = await Relationship.findOne({
$or: [
{ source, target, network: networkId },
{ source: target, target: source, network: networkId },
],
});
if (existingRelationship) {
res.status(400).json({ message: 'A relationship already exists between these people' });
return;
}
const relationship = new Relationship({
source,
target,
type,
customType: type === 'custom' ? customType : undefined,
network: networkId,
});
await relationship.save();
res.status(201).json({ success: true, data: relationship });
} catch (error) {
console.error('Add relationship error:', error);
res.status(500).json({ message: 'Server error' });
} }
const networkId = req.params.networkId;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can add relationships)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can add relationships' });
return;
}
const { source, target, type, customType } = req.body;
// Check if source and target exist and belong to the network
const sourcePerson = await Person.findOne({
_id: source,
network: networkId,
});
const targetPerson = await Person.findOne({
_id: target,
network: networkId,
});
if (!sourcePerson || !targetPerson) {
res.status(400).json({ message: 'Source or target person not found in this network' });
return;
}
// Check if relationship already exists
const existingRelationship = await Relationship.findOne({
$or: [
{ source, target, network: networkId },
{ source: target, target: source, network: networkId },
],
});
if (existingRelationship) {
res.status(400).json({ message: 'A relationship already exists between these people' });
return;
}
const relationship = new Relationship({
source,
target,
type,
customType: type === 'custom' ? customType : undefined,
network: networkId,
});
await relationship.save();
res.status(201).json({ success: true, data: relationship });
} catch (error) {
console.error('Add relationship error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };
// Update a relationship // 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 relationshipId = req.params.id;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can update relationships)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can update relationships' });
return;
}
const relationship = await Relationship.findOne({
_id: relationshipId,
network: networkId,
});
if (!relationship) {
res.status(404).json({ message: 'Relationship not found' });
return;
}
const { type, customType } = req.body;
// Update relationship
if (type) relationship.type = type;
if (type === 'custom' && customType) relationship.customType = customType;
await relationship.save();
res.json({ success: true, data: relationship });
} catch (error) {
console.error('Update relationship error:', error);
res.status(500).json({ message: 'Server error' });
} }
const networkId = req.params.networkId;
const relationshipId = req.params.id;
if (!req.user || !req.network) {
res.status(401).json({ message: 'Not authorized' });
return;
}
// Check if user is the owner (only owners can update relationships)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can update relationships' });
return;
}
const relationship = await Relationship.findOne({
_id: relationshipId,
network: networkId,
});
if (!relationship) {
res.status(404).json({ message: 'Relationship not found' });
return;
}
const { type, customType } = req.body;
// Update relationship
if (type) relationship.type = type;
if (type === 'custom' && customType) relationship.customType = customType;
await relationship.save();
res.json({ success: true, data: relationship });
} catch (error) {
console.error('Update relationship error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };
// Remove a relationship // 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)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can remove relationships' });
return;
}
const relationship = await Relationship.findOne({
_id: relationshipId,
network: networkId,
});
if (!relationship) {
res.status(404).json({ message: 'Relationship not found' });
return;
}
await relationship.deleteOne(); // Changed from remove() to deleteOne()
res.json({ success: true, message: 'Relationship removed successfully' });
} catch (error) {
console.error('Remove relationship error:', error);
res.status(500).json({ message: 'Server error' });
} }
// Check if user is the owner (only owners can remove relationships)
if (req.network.owner.toString() !== req.user._id.toString()) {
res.status(403).json({ message: 'Only the network owner can remove relationships' });
return;
}
const relationship = await Relationship.findOne({
_id: relationshipId,
network: networkId,
});
if (!relationship) {
res.status(404).json({ message: 'Relationship not found' });
return;
}
await relationship.deleteOne(); // Changed from remove() to deleteOne()
res.json({ success: true, message: 'Relationship removed successfully' });
} catch (error) {
console.error('Remove relationship error:', error);
res.status(500).json({ message: 'Server error' });
}
}; };

View File

@ -7,33 +7,34 @@ import { UserRequest } from '../types/express';
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) { if (!token) {
res.status(401).json({ message: 'No token, authorization denied' }); res.status(401).json({ message: 'No token, authorization denied' });
return; return;
}
// Verify token
const decoded = jwt.verify(token, JWT_SECRET) as { id: string };
// Find user by id
const user = await User.findById(decoded.id).select('-password');
if (!user) {
res.status(401).json({ message: 'User not found' });
return;
}
// Set user in request object
req.user = user;
next();
} catch (error) {
res.status(401).json({ message: 'Token is not valid' });
} }
// Verify token
const decoded = jwt.verify(token, JWT_SECRET) as { id: string };
// Find user by id
const user = await User.findById(decoded.id).select('-password');
if (!user) {
res.status(401).json({ message: 'User not found' });
return;
}
// Set user in request object
req.user = user;
next();
} catch (error) {
res.status(401).json({ message: 'Token is not valid' });
}
}; };

View File

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

View File

@ -1,36 +1,35 @@
import mongoose, { Document, Schema } from 'mongoose'; 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: {
type: String,
trim: true,
},
owner: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
},
isPublic: {
type: Boolean,
default: false,
},
}, },
{ timestamps: true } description: {
type: String,
trim: true,
},
owner: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
},
isPublic: {
type: Boolean,
default: false,
},
},
{ timestamps: true }
); );
export default mongoose.model<INetwork>('Network', NetworkSchema); export default mongoose.model<INetwork>('Network', NetworkSchema);

View File

@ -1,49 +1,49 @@
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: {
type: String,
required: [true, 'Last name is required'],
trim: true,
},
birthday: {
type: Date,
},
network: {
type: Schema.Types.ObjectId,
ref: 'Network',
required: true,
},
position: {
x: {
type: Number,
default: 0,
},
y: {
type: Number,
default: 0,
},
},
}, },
{ timestamps: true } lastName: {
type: String,
required: [true, 'Last name is required'],
trim: true,
},
birthday: {
type: Date,
},
network: {
type: Schema.Types.ObjectId,
ref: 'Network',
required: true,
},
position: {
x: {
type: Number,
default: 0,
},
y: {
type: Number,
default: 0,
},
},
},
{ timestamps: true }
); );
// Create compound index to ensure uniqueness within a network // Create compound index to ensure uniqueness within a network

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: {
type: Schema.Types.ObjectId,
ref: 'Person',
required: true,
},
type: {
type: String,
required: [true, 'Relationship type is required'],
enum: ['freund', 'partner', 'familie', 'arbeitskolleg', 'custom'],
},
customType: {
type: String,
trim: true,
},
network: {
type: Schema.Types.ObjectId,
ref: 'Network',
required: true,
},
}, },
{ timestamps: true } target: {
type: Schema.Types.ObjectId,
ref: 'Person',
required: true,
},
type: {
type: String,
required: [true, 'Relationship type is required'],
enum: ['freund', 'partner', 'familie', 'arbeitskolleg', 'custom'],
},
customType: {
type: String,
trim: true,
},
network: {
type: Schema.Types.ObjectId,
ref: 'Network',
required: true,
},
},
{ timestamps: true }
); );
// Create compound index to ensure unique relationships in a network // 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

@ -2,53 +2,53 @@ 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: {
type: String,
required: [true, 'Password is required'],
minlength: 6,
},
username: {
type: String,
required: [true, 'Username is required'],
trim: true,
},
}, },
{ timestamps: true } password: {
type: String,
required: [true, 'Password is required'],
minlength: 6,
},
username: {
type: String,
required: [true, 'Username is required'],
trim: true,
},
},
{ timestamps: true }
); );
// Hash password before saving // 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

@ -9,25 +9,25 @@ const router = express.Router();
// @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

View File

@ -17,11 +17,9 @@ router.get('/', networkController.getUserNetworks);
// @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 // @route GET /api/networks/:id
@ -33,11 +31,9 @@ router.get('/:id', networkController.getNetwork);
// @desc Update a network // @desc Update a network
// @access Private // @access Private
router.put( router.put(
'/:id', '/:id',
[ [check('name', 'Network name is required if provided').optional().not().isEmpty()],
check('name', 'Network name is required if provided').optional().not().isEmpty(), networkController.updateNetwork
],
networkController.updateNetwork
); );
// @route DELETE /api/networks/:id // @route DELETE /api/networks/:id

View File

@ -18,26 +18,26 @@ router.get('/:networkId/people', peopleController.getPeople);
// @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

View File

@ -18,34 +18,40 @@ router.get('/:networkId/relationships', relationshipController.getRelationships)
// @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'))
.not()
.isEmpty(),
],
relationshipController.addRelationship
); );
// @route PUT /api/networks/:networkId/relationships/:id // @route PUT /api/networks/:networkId/relationships/:id
// @desc Update a relationship // @desc Update a relationship
// @access Private // @access Private
router.put( router.put(
'/:networkId/relationships/:id', '/:networkId/relationships/:id',
[ [
check('type', 'Relationship type must be valid if provided') check('type', 'Relationship type must be valid if provided')
.optional() .optional()
.isIn(['freund', 'partner', 'familie', 'arbeitskolleg', 'custom']), .isIn(['freund', 'partner', 'familie', 'arbeitskolleg', 'custom']),
check('customType', 'Custom type is required when type is custom') check('customType', 'Custom type is required when type is custom')
.if(check('type').equals('custom')) .if(check('type').equals('custom'))
.not() .not()
.isEmpty(), .isEmpty(),
], ],
relationshipController.updateRelationship relationshipController.updateRelationship
); );
// @route DELETE /api/networks/:networkId/relationships/:id // @route DELETE /api/networks/:networkId/relationships/:id

View File

@ -11,5 +11,5 @@ 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

@ -4,6 +4,6 @@ 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;
} }

376
yarn.lock

File diff suppressed because it is too large Load Diff