OpeNotes — notatnik z logowaniem przez Google
Wieloużytkownikowa aplikacja SPA do tworzenia, edytowania i usuwania notatek. Rejestracja i logowanie e-mailem (bcrypt, Passport.js) oraz jednym kliknięciem przez Google OAuth 2.0. Notatki zapisane w PostgreSQL, stan zalogowania utrzymywany przez sesje serwerowe z httpOnly cookie.
01Wygląd aplikacji
Estetyka Google Keep — żółty header (#F5BA13), białe karty notatek na szachownicowym tle,
Montserrat jako font, logo z ikoną żarówki (MUI TungstenIcon). Cztery oddzielne widoki
połączone w SPA przez React Router.
Your personal notes app
Place where you can keep your notes organized
strona główna — widok dla niezalogowanych (/)
with Google
ekran logowania — email/hasło + Google OAuth (/login)
Shopping list
Milk, eggs, bread, coffee, butter, apples
Book ideas
Atomic Habits, Deep Work, The Pragmatic Programmer…
Workout plan
Mon: chest · Wed: legs · Fri: back + arms
panel notatek — formularz create + karty z edycją i usuwaniem (/notebook)
dialog edycji — MUI Dialog z TextField fullWidth (/notebook)
02Funkcjonalności
- Rejestracja — walidacja frontendowa (email format, min. 6 znaków, zgodność haseł) + walidacja po stronie serwera + bcrypt hash z 10 rundami soli. Po rejestracji auto-login przez
req.login(). - Logowanie e-mailem — Passport Local Strategy,
bcrypt.compare(), sesja serwerowa httpOnly. Rate limiter: max 3 próby / 15 minut (kody HTTP 429). - Logowanie Google — OAuth 2.0 przez
passport-google-oauth2. Jeśli e-mail istnieje — logowanie, jeśli nie — automatyczna rejestracja. - CRUD notatek — dodawanie, wyświetlanie, edycja przez MUI Dialog, usuwanie. Optimistic UI update przy dodawaniu i usuwaniu (brak reload strony), pełny reload po edycji.
- Persystencja sesji —
GET /api/check-sessionwywoływany przy starcie App, przywraca stan zalogowania po odświeżeniu strony. - Ochrona routów — React Router z
Navigate: zalogowany → przekierowanie z/loginna/notebook; niezalogowany → z/notebookna/login.
03Architektura — SPA + REST API
Klasyczna architektura rozdzielonego frontendu i backendu. React SPA (port 5173) komunikuje się z Express REST API (port 3000) przez Axios z withCredentials: true — to kluczowe dla sesyjnych cookies w środowisku cross-origin.
React SPA (Vite :5173) Express API (:3000) PostgreSQL
│ │ │
│ axios.get('/api/check-session')│ │
│ withCredentials: true ────────▶│ │
│ │ req.isAuthenticated() │
│◀─────────────── {loggedIn: true, user} ──────────────────────│
│ │ │
│ axios.get('/api/shownotes') ──▶│ SELECT FROM notes │
│ │ WHERE userid = $1 ───────────▶│
│◀─────────────── {data: [...notes]} ◀─────────────────────────│
│ │ │
│ axios.post('/api/postnote') ──▶│ INSERT INTO notes │
│ │ VALUES (...) ────────────────▶│
│◀─────────────── 201 Created ◀─────────────────────────────────│
04Autentykacja — Passport + sesje + OAuth
Trzy ścieżki autentykacji obsługiwane przez Passport.js w jednym pliku backendowym.
// Sesja httpOnly — niedostępna dla JavaScript w przeglądarce
app.use(session({
secret: process.env.SESSION_SECRET,
cookie: {
httpOnly: true,
sameSite: "lax",
maxAge: 60 * 60 * 1000 // 1 godzina
}
}));
// Rate limiter: blokada po 3 nieudanych próbach / 15 minut
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 3,
message: { message: "Too many login attempts. Try again later." }
});
// Google OAuth — auto-rejestracja jeśli email nie istnieje w bazie
const result = await db.query("SELECT * FROM users WHERE email = $1", [profile.email]);
if (result.rows.length === 0) {
const newUser = await db.query(
"INSERT INTO users (email, password) VALUES ($1, $2) RETURNING *",
[profile.email, "google"] // placeholder hasła dla kont OAuth
);
return done(null, newUser.rows[0]);
}
Frontend obsługuje kody HTTP z API: status 401 → “Invalid credentials”, status 429 → “Too many login attempts. Try again later.” — komunikaty błędów przekazywane z backendu do stanu errors w komponencie.
05Architektura React
Aplikacja zbudowana jako SPA z 8 komponentami funkcyjnymi. Stan zalogowanego użytkownika
(user) przechowywany w App.jsx i przekazywany propami do komponentów.
Hooki useState + useEffect do lokalnego stanu i efektów ubocznych.
- CreateArea — rozwijany formularz (collapsed → expanded) z animacją MUI
Zoom. Pole tytułu pojawia się dopiero po kliknięciu textarea. Floating Action Button z ikoną+do zapisu. - Note — karta z przyciskami MUI
DeleteIconiModeEditIcon. Kliknięcie edycji ustawiaisEditing: true, co otwiera dialog. - NoteEditorDialog — MUI
DialogfullWidth zTextFielddla tytułu (single line) i treści (multiline 4 rows). Lokalny stantitle/contentsynchronizowany z propsami przezuseEffect([open, note]). - Notebook — pobiera notatki przez Axios przy
useEffect([], [])(raz po mount). Optimistic update: po dodaniu notatkisetUserNotes(prev => [...prev, newNote])bez dodatkowego fetcha.
// CreateArea — expand on click, Zoom animation on FAB
function CreateArea(props) {
const [isExpanded, setExpanded] = useState(false);
return (
<form className="create-note">
{isExpanded && <input name="title" placeholder="Title" />} {/* pojawia się po kliknięciu */}
<textarea
onClick={() => setExpanded(true)} {/* kliknięcie → expand */}
rows={isExpanded ? 3 : 1}
placeholder="Take a note..."
/>
<Zoom in={isExpanded}> {/* FAB pojawia się z animacją */}
<Fab onClick={submitNote}><AddIcon /></Fab>
</Zoom>
</form>
);
}
06REST API — endpointy
Rejestracja — walidacja pól, bcrypt hash, auto-login przez
req.login(), zwraca user objectLogowanie — Passport local, rate limited (3/15min), sesja httpOnly cookie
Inicjalizacja Google OAuth 2.0 — redirect do Google consent screen
Callback OAuth — auto-rejestracja lub logowanie, redirect do
/notebookSprawdzenie sesji przy starcie SPA — zwraca
{loggedIn, user}Wylogowanie —
req.logout() + session.destroy()Notatki zalogowanego użytkownika —
SELECT WHERE userid = $1Dodanie notatki —
INSERT INTO notes, zwraca status 201Edycja tytułu i treści po
noteid — UPDATE notes SET title, contentUsunięcie notatki po
id z body — DELETE FROM notes WHERE noteid = $107Stack technologiczny
Navigate): /, /login, /register, /notebook.withCredentials: true dla cookies cross-origin.08Czego dowodzi ten projekt
Kompletny SPA
React Router z ochroną routów, session check przy starcie, optimistic updates — pełny cykl SPA.
Passport multi-auth
Local strategy (bcrypt) + Google OAuth 2.0 w jednym serwisie, auto-rejestracja przez OAuth.
Sesje + cross-origin
httpOnly cookie między :5173 a :3000 z CORS credentials: true — klasyczna architektura dev.
Pełne CRUD
GET, POST, PATCH, DELETE — wszystkie metody HTTP z proper status codes (201, 401, 404, 429, 500).
MUI Dialog pattern
Controlled dialog z lokalnym stanem synchronizowanym przez useEffect — typowy wzorzec React UI.
Punkt wyjścia
OpeNotes to baza, na której zbudowany jest Minds AIssistant — ten sam stack plus AI coach i kalendarz.
Zobacz kod na GitHubie
Repozytorium zawiera pełny frontend React i backend Express z Passport.js, Google OAuth i PostgreSQL.
Zobacz kod na GitHub