This document explains how different parts of the Hiking Logbook system interact with each other for Sprint 1, focusing on user authentication and basic user profile storage.
What’s Included:
What’s NOT Included (Future Sprints):
┌─────────────────┐ HTTP/HTTPS ┌─────────────────┐
│ Frontend │ ◄──────────────► │ Backend │
│ (React) │ │ (Express.js) │
└─────────────────┘ └─────────────────┘
│ │
│ │
│ Firebase SDK │ Firebase Admin SDK
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Firebase Auth │ │ Firebase Admin │
│ (Client SDK) │ │ (Server SDK) │
└─────────────────┘ └─────────────────┘
│ │
│ │
└─────────────┬───────────────────────┘
│
▼
┌─────────────────┐
│ Firebase │
│ Firestore │
│ (Database) │
└─────────────────┘
sequenceDiagram
participant U as User
participant F as Frontend
participant B as Backend
participant FA as Firebase Auth
participant DB as Firestore
U->>F: Fill signup form
F->>B: POST /auth/signup
B->>FA: Create user account
FA-->>B: User created (UID)
B->>DB: Create user profile
DB-->>B: Profile saved
B-->>F: Success response + UID
F-->>U: Show success message
sequenceDiagram
participant U as User
participant F as Frontend
participant B as Backend
participant DB as Firestore
U->>F: Navigate to profile
F->>B: GET /users/:uid
B->>DB: Query user profile
DB-->>B: Profile data
B-->>F: User profile data
F->>F: Update UI with profile
F-->>U: Display profile information
Purpose: Create new user account and profile
Frontend Request:
const response = await fetch("/auth/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "user@example.com",
password: "password123",
displayName: "John Doe",
bio: "Hiking enthusiast",
}),
});
Backend Processing:
// 1. Validate input data
// 2. Create user in Firebase Auth
// 3. Create profile in Firestore
// 4. Return success response
Response Handling:
if (response.ok) {
const data = await response.json();
// Handle success - redirect to profile or dashboard
} else {
// Handle error - show error message
}
Purpose: Retrieve user profile by UID
Frontend Request:
const response = await fetch(`/users/${uid}`);
Backend Processing:
// 1. Validate UID parameter
// 2. Query Firestore for user profile
// 3. Return profile data or error
Response Handling:
if (response.ok) {
const profile = await response.json();
// Update UI with profile data
} else {
// Handle error - user not found, etc.
}
{
"error": "Validation failed",
"details": "Email is required"
}
{
"error": "User already exists"
}
{
"error": "User not found"
}
{
"error": "Internal server error"
}
try {
const response = await fetch("/auth/signup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userData),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error);
}
// Handle success
} catch (error) {
// Display error message to user
setError(error.message);
}
const [authState, setAuthState] = useState({
isAuthenticated: false,
user: null,
loading: false,
error: null,
});
// After successful signup
setAuthState({
isAuthenticated: true,
user: { uid: response.uid, email: userData.email },
loading: false,
error: null,
});
// After error
setAuthState((prev) => ({
...prev,
loading: false,
error: error.message,
}));
const [formData, setFormData] = useState({
email: "",
password: "",
displayName: "",
bio: "",
});
const handleInputChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};
const validateForm = () => {
const errors = [];
if (!email || !isValidEmail(email)) {
errors.push("Valid email is required");
}
if (!password || password.length < 8) {
errors.push("Password must be at least 8 characters");
}
if (!displayName || displayName.length < 2) {
errors.push("Display name must be at least 2 characters");
}
return errors;
};
const validateSignupData = (data) => {
const errors = [];
if (!data.email || !isValidEmail(data.email)) {
errors.push("Valid email is required");
}
if (!data.password || data.password.length < 8) {
errors.push("Password must be at least 8 characters");
}
if (!data.displayName || data.displayName.length < 2) {
errors.push("Display name must be at least 2 characters");
}
return errors;
};
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null &&
request.auth.uid == userId;
}
}
}
// Test signup form submission
test("should submit signup form with valid data", async () => {
render(<SignupForm />);
fireEvent.change(screen.getByLabelText(/email/i), {
target: { value: "test@example.com" },
});
fireEvent.click(screen.getByRole("button", { name: /sign up/i }));
await waitFor(() => {
expect(screen.getByText(/success/i)).toBeInTheDocument();
});
});
// Test signup endpoint
test("POST /auth/signup should create new user", async () => {
const userData = {
email: "test@example.com",
password: "password123",
displayName: "Test User",
};
const response = await request(app).post("/auth/signup").send(userData);
expect(response.status).toBe(201);
expect(response.body).toHaveProperty("uid");
});
// Backend CORS configuration
app.use(
cors({
origin: "http://localhost:3000",
credentials: true,
})
);
// Check Firebase configuration
const serviceAccount = require("./serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
*This module interactions document covers Sprint 1 only - user authentication and basic profile storage.