A full-stack web application for managing memos and reminders with end-to-end encryption, email notifications, and speech-to-text dictation.
βββββββββββββββββββ
β Frontend β
β (HTML/JS/CSS) β
β Port: N/A β
ββββββββββ¬βββββββββ
β HTTPS
βΌ
βββββββββββββββββββ
β Backend API β
β Spring Boot β
β Port: 8081 β
ββββββββββ¬βββββββββ
β JDBC
βΌ
βββββββββββββββββββ
β PostgreSQL β
β Database β
β Port: 5432 β
βββββββββββββββββββ
Memobee/
βββ backend/
β βββ src/
β β βββ main/
β β β βββ java/com/mariusz/demo/
β β β β βββ DemoApplication.java
β β β β βββ controller/
β β β β β βββ UserController.java
β β β β β βββ NoteController.java
β β β β βββ model/
β β β β β βββ User.java
β β β β β βββ Note.java
β β β β β βββ PasswordResetToken.java
β β β β βββ repository/
β β β β β βββ UserRepository.java
β β β β β βββ NoteRepository.java
β β β β β βββ PasswordResetTokenRepository.java
β β β β βββ service/
β β β β β βββ EmailService.java (Resend HTTP API sending)
β β β β β βββ ReminderScheduler.java (5-min reminder processing)
β β β β βββ security/
β β β β βββ JwtUtil.java
β β β β βββ JwtFilter.java
β β β β βββ SecurityConfig.java
β β β β βββ LoginRateLimiter.java
β β β βββ resources/
β β β βββ application.properties
β β β βββ schema.sql
β β βββ test/
β β βββ java/com/mariusz/demo/
β β β βββ SecurityIntegrationTest.java
β β β βββ security/
β β β βββ JwtUtilTest.java
β β β βββ LoginRateLimiterTest.java
β β βββ resources/
β β βββ application.properties (test-only config, no DB required)
β βββ pom.xml
β βββ nixpacks.toml
β βββ railway.json
βββ frontend/
β βββ index.html (login / self-registration with encryption warning / forgot password)
β βββ dashboard.html (admin panel - read-only user list with stats)
β βββ user.html (user panel - encrypted memos / reminders / archive / change password)
β βββ reset.html (password reset page)
β βββ _headers (Railway cache-control headers)
βββ .github/
β βββ workflows/
β βββ ci.yml
βββ README.md
git clone <your-repo-url>
cd "Memobee/backend"
Configure local database
Create a PostgreSQL database and set environment variables:
export PGHOST=localhost
export PGPORT=5432
export PGDATABASE=demo_db
export PGUSER=postgres
export PGPASSWORD=your_password
export PORT=8081
mvn clean install
mvn spring-boot:run
curl http://localhost:8081/api/users/health
# Should return: "Backend is running!"
cd ../frontend
python -m http.server 8000
http://localhost:8000
Railway deployment requires manual configuration in the Railway dashboard. This is not fully automated CI/CD.
Configure Backend Service
In the backend service settings:
a) Set Root Directory
backendb) Add Environment Variables (Variables tab)
PORT = 8081
PGHOST = $
PGPORT = $
PGDATABASE = $
PGUSER = $
PGPASSWORD = $
JWT_SECRET = <random string, min 32 characters>
c) Configure Networking
8081If you want to deploy the frontend separately:
frontendAll endpoints except login and health require Authorization: Bearer <token> header.
Admin-only endpoints additionally require the admin role in the token.
GET /api/users/health
POST /api/users/login body: { email, password }
POST /api/users/register body: { name, email, password }
POST /api/users/forgot-password body: { email }
POST /api/users/reset-password-token body: { token, newPassword }
POST /api/users/change-password body: { email, oldPassword, newPassword }
GET /api/users list all users (includes noteCount, createdAt, lastLoginAt)
DELETE /api/users/{id} delete user
Admin account is created automatically at startup via AdminSeeder when ADMIN_EMAIL and ADMIN_PASSWORD environment variables are set.
Users can only access their own notes. Admins can access any userβs notes.
GET /api/notes/user/{email} list notes for user (own or admin)
POST /api/notes create note (userEmail set from JWT, not request body)
PUT /api/notes/{id} update note (own or admin)
DELETE /api/notes/{id} delete note (own or admin)
Note body fields by type (text field is encrypted client-side before sending):
| Field | Memo | Reminder |
|---|---|---|
type |
"memo" |
"reminder" |
title |
β plaintext | β plaintext β sent in reminder emails |
text |
β encrypted | β encrypted β NOT sent by email |
frequency |
never/daily/weekly/monthly/quarterly/yearly (default: never) |
always "never" |
reminderAt |
β | ISO datetime UTC (e.g. "2026-03-29T12:00:00") |
repeatUntilDeleted |
β | true (repeat) / false (one-time) |
repeatDays |
β | integer β₯ 0 (default 0) |
repeatHours |
β | integer β₯ 0 (default 0) |
Run all 38 backend tests locally:
cd backend
mvn test
Tests use a dedicated src/test/resources/application.properties that disables datasource auto-configuration β no PostgreSQL instance required. All repositories are mocked via @MockitoBean.
Test suites:
| Suite | Type | Tests | Covers |
|---|---|---|---|
SecurityIntegrationTest |
Integration (@SpringBootTest + MockMvc) |
23 | Public endpoint access, unauthenticated rejection, admin-only authorization, note ownership enforcement, login/registration validation, note input validation |
JwtUtilTest |
Unit | 8 | Token generation, claims extraction, expiration, tampered/invalid/wrong-key tokens |
LoginRateLimiterTest |
Unit | 7 | IP-based blocking after 5 failures, reset on success, IP isolation, lockout timer |
https://pretty-illumination-production.up.railway.appBoth jobs run in parallel on every push/PR to main:
Job: validate-html
Job: test-backend
mvn test)View workflow: .github/workflows/ci.yml
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255),
role VARCHAR(50) DEFAULT 'user',
encryption_salt VARCHAR(255), -- immutable UUID for client-side encryption key derivation
created_at TIMESTAMP, -- UTC account creation time
last_login_at TIMESTAMP -- UTC last successful login
);
CREATE TABLE notes (
id BIGSERIAL PRIMARY KEY,
user_email VARCHAR(255) NOT NULL,
created_at TEXT,
type VARCHAR(20) NOT NULL, -- memo | reminder
title VARCHAR(255), -- plaintext title, used in reminder emails
text TEXT, -- encrypted client-side (ENC:base64iv.base64ciphertext)
frequency VARCHAR(20) DEFAULT 'never', -- memo: never/daily/weekly/monthly/quarterly/yearly
attachment_name VARCHAR(255),
attachment_type VARCHAR(50),
attachment_data TEXT, -- Base64 encoded
last_sent_at TIMESTAMP, -- set after each email is sent
-- reminder-specific columns:
reminder_at TIMESTAMP, -- UTC datetime to fire the reminder
repeat_until_deleted BOOLEAN DEFAULT FALSE,
repeat_days INTEGER DEFAULT 0,
repeat_hours INTEGER DEFAULT 0,
repeat_quarters INTEGER DEFAULT 0, -- legacy, no longer used in UI
FOREIGN KEY (user_email) REFERENCES users(email) ON DELETE CASCADE
);
CREATE TABLE password_reset_tokens (
id BIGSERIAL PRIMARY KEY,
user_email VARCHAR(255) NOT NULL,
token VARCHAR(255) NOT NULL UNIQUE,
expires_at TIMESTAMP NOT NULL, -- UTC, 15-minute validity
FOREIGN KEY (user_email) REFERENCES users(email) ON DELETE CASCADE
);
| Variable | Description | Example |
|---|---|---|
PORT |
Application port | 8081 |
PGHOST |
PostgreSQL host | $ |
PGPORT |
PostgreSQL port | $ |
PGDATABASE |
Database name | $ |
PGUSER |
Database user | $ |
PGPASSWORD |
Database password | $ |
JWT_SECRET |
HMAC-SHA signing key (min 32 chars) | your-random-secret |
ADMIN_EMAIL |
Initial admin account email (optional) | admin@example.com |
ADMIN_PASSWORD |
Initial admin account password (optional) | Str0ng!Pass |
RESEND_API_KEY |
Resend HTTP API key for sending emails | re_... |
MAIL_FROM |
Sender address (verified domain required) | reminder@memobee.eu |
FRONTEND_URL |
Frontend base URL (for password reset links) | https://your-frontend.up.railway.app |
The frontend automatically detects whether itβs running locally or on Railway and uses the appropriate backend URL.
Cause: Port mismatch between app and Railway configuration
Solution:
PORT=8081 is set in Railway variables8081Cause: Missing or incorrect database environment variables
Solution:
PG* variables are set in Railway$Cause: Backend not allowing cross-origin requests
Solution: Already configured with @CrossOrigin(origins = "*") in UserController.java
backend/nixpacks.tomlConfigures Railwayβs Nixpacks build system:
mvn clean packagebackend/railway.jsonDefines Railway deployment settings:
backend/application.propertiesSpring Boot configuration:
PORT env var)PG* env vars)backend/src/test/resources/application.propertiesTest-only Spring Boot configuration:
.github/workflows/ci.ymlGitHub Actions workflow:
validate-html: validates HTML structure + W3C validationtest-backend: runs all backend tests with MavenNote text is encrypted client-side before being sent to the server. The database only stores ciphertext β admins cannot read user content.
How it works:
encryptionSalt (UUID) is generated and stored permanently in the databasesessionStorage and used to derive an AES-256-GCM key via PBKDF2 (600,000 iterations, SHA-256)ENC: prefix + base64(IV) + . + base64(ciphertext)Title vs. Text:
Limitations:
Mariusz Puto
Copyright (c) 2026 Mariusz Puto. All rights reserved.
This source code is published for portfolio and demonstration purposes only. No part of this repository may be reproduced, copied, modified, distributed, or used without prior written permission from the author. See LICENSE for details.