- Never commit unless explicitly told to. Wait for the user to ask you to commit changes.
DailyNotes is a self-hosted daily task and note-taking application that combines the experience of a physical planner with modern web technology. It supports markdown with GitHub Flavored Markdown (GFM) task lists, making it ideal for daily journaling, task tracking, and note management.
Version Format: YYYY.MM.DD-## (date-based with daily build number, managed by CI)
- Framework: Quart (async Python microframework, Flask-compatible)
- Database ORM: SQLAlchemy (raw, without Flask-SQLAlchemy)
- Authentication: JWT (JSON Web Tokens) via PyJWT with custom decorators
- Password Hashing: Argon2 via argon2-cffi
- Database Migrations: Alembic
- Production Server: Uvicorn (ASGI)
- HTTP Client: httpx (async HTTP requests)
- Data Encryption: PyCryptodome (AES encryption for sensitive data at rest)
- Markdown Processing: python-frontmatter (for parsing YAML frontmatter)
Python Version: Python 3.8+
- Framework: Vue.js 2.6
- Language: TypeScript 3.5
- UI Library: Buefy (Vue wrapper for Bulma CSS)
- CSS Framework: Bulma with Bulmaswatch theme (Minty)
- Editor: CodeMirror 5 (for markdown editing with syntax highlighting)
- Markdown Rendering: Marked (for HTML preview with GFM support)
- Routing: Vue Router 3
- HTTP Client: Axios with JWT interceptors
- Utilities: date-fns, Lodash, Vue Masonry CSS
- Icons: FontAwesome Free
- Fonts: Fira Code (with ligatures), Montserrat
Node Version: 12+
DailyNotes/
├── app/ # Backend Python application
│ ├── __init__.py # Quart app initialization
│ ├── routes.py # API endpoints
│ ├── models.py # Database models (User, Note, Meta)
│ └── model_types.py # Custom SQLAlchemy types (GUID)
├── client/ # Frontend Vue.js application
│ ├── src/
│ │ ├── main.ts # Vue app entry point
│ │ ├── App.vue # Root Vue component
│ │ ├── interfaces.ts # TypeScript interfaces (INote, IHeaderOptions, IMeta)
│ │ ├── router/
│ │ │ └── index.ts # Vue Router configuration & auth guards
│ │ ├── services/ # API service layer
│ │ │ ├── requests.ts # Axios configuration with JWT interceptors
│ │ │ ├── notes.ts # Note/day API service methods
│ │ │ ├── user.ts # User auth service
│ │ │ ├── sidebar.ts # Sidebar state management
│ │ │ ├── theme.ts # Theme service (light/dark/system)
│ │ │ ├── localstorage.ts # Local storage utilities
│ │ │ ├── consts.ts # Template constants
│ │ │ ├── eventHub.ts # Vue event bus
│ │ │ └── sharedBuefy.ts # Shared Buefy notification/dialog
│ │ ├── components/ # Reusable Vue components
│ │ │ ├── Editor.vue # CodeMirror markdown editor
│ │ │ ├── MarkdownPreview.vue # HTML preview for markdown content
│ │ │ ├── Header.vue # Page header with nav & controls
│ │ │ ├── Calendar.vue # Date picker calendar
│ │ │ ├── NoteCard.vue # Note display card
│ │ │ ├── Tasks.vue # Task list component
│ │ │ ├── Tags.vue # Tag display component
│ │ │ ├── SimpleTask.vue # Individual task item
│ │ │ ├── TaskItem.vue # Task in list format
│ │ │ └── UnsavedForm.vue # Unsaved changes dialog
│ │ └── views/ # Page-level Vue components (routed)
│ │ ├── Home.vue # Main layout with sidebar
│ │ ├── Day.vue # Daily note editor
│ │ ├── Note.vue # Note detail page
│ │ ├── NewNote.vue # Create new note
│ │ ├── Search.vue # Search page
│ │ ├── Auth.vue # Auth layout
│ │ ├── Login.vue # Login form
│ │ ├── Signup.vue # Registration form
│ │ └── Error pages # 404, 401, error pages
│ ├── package.json # Frontend dependencies & scripts
│ └── tsconfig.json # TypeScript configuration
├── config/ # Configuration directory
│ ├── app.db # SQLite database (default)
│ ├── .env # Environment variables (generated)
│ └── export.zip # Export storage location
├── migrations/ # Database migration files (Alembic)
├── config.py # Quart configuration
├── server.py # Quart app entry point (for uvicorn)
├── requirements.txt # Python dependencies
├── run.sh # Production startup script
├── Dockerfile # Docker image definition
├── docker-compose.yml # Docker compose configuration
└── README.md # Project documentation
-
Frontend (Vue.js)
- User interacts with Vue components
- Components dispatch API calls via service layer (NoteService, user service)
- Requests module adds JWT token to Authorization header
- Responses are parsed and component state is updated
-
API Layer (requests.ts)
- Axios HTTP client with interceptors
- Automatically adds JWT Bearer token from localStorage
- Handles 401/403/422 responses by logging user out and redirecting to login
- Provides wrapper methods:
post(),get(),put(),delete(),download()
-
Backend (Quart)
- Routes receive requests at
/api/*endpoints @jwt_required()decorator validates JWT token- Extract user identity from JWT with
get_jwt_identity() - Query database for user and related data
- Return JSON responses
- Routes receive requests at
-
Database
- SQLAlchemy ORM for data access
- All sensitive text fields encrypted with AES before storage
- Automatic title extraction from markdown frontmatter
- Automatic meta extraction (tags, projects, tasks) on note save
User
uuid- Primary keyusername- Unique login identifierpassword_hash- Argon2 hashed passwordauto_save- Boolean for auto-save preference- Relationships:
notes,meta(cascading delete)
Note
uuid- Primary keyuser_id- Foreign key to Userdata- Encrypted markdown contenttitle- Encrypted title (extracted from frontmatter or derived)date- Created timestampis_date- Boolean (True for daily notes)- Relationships:
meta(tags, projects, tasks)
Meta (Tags, Projects, Tasks)
uuid- Primary keyuser_id- Foreign key to Usernote_id- Foreign key to Notename_encrypted- Encrypted name/textname_compare- For task update trackingkind- "tag", "project", or "task"
- Uses AES encryption (PyCrypto library)
- Encryption key from
DB_ENCRYPTION_KEYenvironment variable - All user data encrypted at rest in database
- Two encryption methods: new (CFB mode) and old (legacy compatibility)
- Decryption happens transparently via SQLAlchemy
@hybrid_property
Files use YAML frontmatter format:
---
title: Daily Notes
tags: personal, productivity
projects: life
---
## Main content
- [ ] Task item
- [x] Completed taskFrontmatter is parsed with python-frontmatter:
titlefield auto-updates note titletagsfield populates tag metadataprojectsfield populates project metadata- Tasks extracted from markdown checkbox pattern
- [ ]or- [x]
POST /api/sign-up- Register new userPOST /api/login- Login and get JWT tokenGET /api/refresh_jwt- Refresh JWT token
GET /api/date?date=MM-dd-yyyy- Get daily note for dateGET /api/note?uuid=...- Get specific note by UUIDPUT /api/save_day- Save daily notePOST /api/create_note- Create new notePUT /api/save_note- Update note contentDELETE /api/delete_note/{uuid}- Delete note
GET /api/sidebar- Get all notes, tags, projects, tasksGET /api/events- Get list of dates with notes (for calendar)POST /api/search- Search notes by project, tag, or text content
POST /api/toggle_auto_save- Toggle auto-save settingGET /api/export- Download all notes as ZIP file
GET /and/<path>- Serves Vue.js SPA (index.html)
npm run serve # Development server with hot reload
npm run build # Production build
npm run test:unit # Run unit tests
npm run lint # Run ESLint./verify_env.py # Generate/verify environment variables
./verify_data_migrations.py # Migrate encryption keys if needed
alembic -c migrations/alembic.ini upgrade head # Run pending migrations
uvicorn server:app --host 0.0.0.0 --port 8000 # Start production server# Terminal 1: Start backend
./run.sh # Runs uvicorn ASGI server
# Terminal 2: Start frontend
cd client && npm run serve# Using Docker
docker-compose up
# Or with Docker run
docker run -p 8000:8000 -v /config_dir:/app/config m0ngr31/dailynotes| Variable | Purpose | Default |
|---|---|---|
API_SECRET_KEY |
Signs JWT tokens | Generated automatically |
DB_ENCRYPTION_KEY |
Encrypts data at rest | Generated automatically |
DATABASE_URI |
Database connection string | SQLite in /config/app.db |
PREVENT_SIGNUPS |
Disable signup endpoint | Not set (signups enabled) |
BASE_URL |
URL prefix for reverse proxy | None |
PUID / PGID |
Docker user/group IDs | None |
VUE_APP_BASE_URL |
Frontend API base URL | /api |
- config.py - Quart configuration management
- requirements.txt - Python package versions
- client/package.json - Node package versions and scripts
- client/tsconfig.json - TypeScript compiler settings
- Dockerfile - Multi-stage: Python + Node build → Uvicorn
- .env - Generated at runtime with encryption keys (DO NOT commit)
-
Install dependencies:
pip install -r requirements.txt cd client && npm ci
-
Initialize environment:
./verify_env.py # Creates /config/.env with generated keys -
Run both servers:
- Terminal 1:
./run.sh(Uvicorn on http://localhost:8000) - Terminal 2:
cd client && npm run serve(Webpack on http://localhost:8080)
- Terminal 1:
- Migrations stored in
/migrations/directory - Add schema change with:
alembic -c migrations/alembic.ini revision --autogenerate -m "description" - Apply migration with:
alembic -c migrations/alembic.ini upgrade head - Uses SQLAlchemy declarative ORM
- User submits credentials to
/api/sign-upor/api/login - Backend generates JWT token with username as identity
- Frontend stores token in localStorage
- Axios interceptor adds
Authorization: Bearer <token>to all requests - On auth failure (401/403), token cleared, user redirected to login
- Token auto-refreshed via
/api/refresh_jwton demand
- Protected routes require valid JWT (checked in router guards)
- Login/signup pages accessible only without token
- Dashboard and note pages require authentication
- Route transitions handled by Vue Router with auth guards
- JWT Authentication - Stateless token-based auth
- Encrypted At Rest - AES encryption for all user data
- Event Hooks - SQLAlchemy event listeners auto-update metadata
- Service Layer - Centralized API calls in
/client/src/services/ - Component Composition - Reusable Vue components (Editor, Header, etc.)
- Markdown-First - All content in markdown with frontmatter metadata
- Single Page App - Vue Router client-side navigation
- Auto-Save - Optional auto-save toggle per user
- Syntax-Based Search - Query parser with tag/project filters and text search
- Export - Download all notes as ZIP with markdown files
- HTML Preview - Real-time markdown preview with VS Code-style hotkeys
- Theming - Light/Dark/System theme support via CSS variables
The Search feature provides a unified syntax-based search interface with autocomplete and result highlighting.
The search supports the following syntax:
tag:valueort:value- Filter by tagproject:valueorp:value- Filter by projecttag:"multi word"- Quoted values for spaces- Plain text - Full-text search across note content
- Combined:
tag:meeting project:work budget- Multiple filters together
- Multiple tags = AND (note must have all specified tags)
- Multiple projects = OR (note can be in any specified project)
- Multiple text terms = AND (note must contain all words)
Tags support hierarchical organization using / as a delimiter:
Syntax:
---
tags: home/family, home/tech, work/meetings, work/projects
---Sidebar Display:
- Nested tags appear as a collapsible tree in the sidebar
- Parent nodes show a chevron toggle (▶/▼) to expand/collapse
- Clicking any tag (parent or child) triggers a search
Search Behavior:
- Searching for a parent tag matches all children
tag:homematches notes withhome,home/family,home/tech, etc.tag:home/familymatches only that specific nested tag- Existing flat tags (without
/) work unchanged
Implementation:
ITagNodeinterface ininterfaces.tsdefines the tree structurebuildTagTree()insidebar.tsconverts flat tags to a nested treeNestedTags.vuecomponent renders the collapsible tree UI- Backend search uses prefix matching:
tag.startswith(search_tag + "/")
Backend (app/routes.py):
-
parse_search_query(query_string)- Parses search syntax into structured filters- Returns
{tags: [], projects: [], text_terms: []} - Supports shorthand
t:andp:prefixes - Handles quoted values for multi-word tags/projects
- Returns
-
get_text_snippet(text, search_terms, context_chars=50)- Extracts snippet around matches- Returns
{snippet: str, highlights: [matched_terms]} - Shows context around first match
- Returns
-
/api/searchendpoint accepts:- New format:
{query: "tag:meeting budget"} - Legacy format:
{selected: "tag", search: "meeting"}(backward compatible)
- New format:
Frontend (client/src/views/Search.vue):
- Single text input with autocomplete dropdown
- Autocomplete triggers on
tag:,t:,project:,p:prefixes - Keyboard navigation: Arrow keys, Tab/Enter to select, Escape to close
- Clear button (X) to reset search and results
- Syntax help tooltip (?) with quick reference
- Results persist when navigating away and returning
State Management (client/src/services/sidebar.ts):
searchQuery- Stores the current query stringfilteredNotes- Stores search results with snippets/highlights- Results include
snippetandhighlightsfields for display
NoteCard Component (client/src/components/NoteCard.vue):
- Displays search snippets with highlighted matching terms
- Uses
<mark>tags for highlighting - Escapes HTML to prevent XSS
- New:
/search?q=tag:meeting+budget - Legacy:
/search?tag=meeting,/search?project=work,/search?search=text
The HTML Preview feature allows users to view their markdown content rendered as HTML in real-time, with two display modes and VS Code-style keyboard shortcuts.
-
Side-by-Side Preview (
Cmd+KthenV)- Editor and preview displayed side-by-side
- 50/50 split view with synchronized scrolling
- Ideal for writing while seeing the formatted output
- Available in both Day.vue and Note.vue views
-
Preview Only (
Shift+Cmd+V)- Full-screen preview with editor hidden
- Clean reading view of formatted markdown
- Useful for reviewing final output
- Toggle again to return to editor
-
Close Preview
- Click the preview icon dropdown and select "Close Preview"
- Or toggle the current mode again to close
Components:
MarkdownPreview.vue- Main preview component- Uses the
markedlibrary for markdown-to-HTML conversion - Configured for GitHub Flavored Markdown (GFM)
- Supports task lists, tables, code blocks, and all standard markdown features
- Dark theme styling to match the CodeMirror editor
- Uses the
Keyboard Shortcuts:
Cmd+K V(macOS) orCtrl+K V(Windows/Linux) - Toggle side-by-side previewShift+Cmd+V(macOS) orShift+Ctrl+V(Windows/Linux) - Toggle preview-only mode- Sequential key detection with 1-second timeout for
Cmd+KthenVpattern
UI Controls:
- Eye icon in header (right side, before save button)
- Dropdown menu with three options:
- Preview Side-by-Side
- Preview Only
- Close Preview (when active)
- Icon changes color when preview is active
Styling:
- Dark background (
#263238) matching CodeMirror theme - Syntax-highlighted code blocks
- Styled tables, blockquotes, and task lists
- Typography optimized for readability
- Responsive layout with proper spacing
State Management:
- Preview mode stored in component state (
previewMode: 'none' | 'side' | 'replace') - Synced with
headerOptions.previewModefor UI updates - Live updates as user types in editor
- Preview renders
modifiedText(unsaved changes) ortext(saved content)
Both Day.vue and Note.vue support the preview feature:
// State
public previewMode: 'none' | 'side' | 'replace' = 'none';
// Header options
public headerOptions: IHeaderOptions = {
showPreview: true,
previewMode: 'none',
togglePreviewFn: (mode) => this.togglePreview(mode),
// ... other options
}
// Toggle method
public togglePreview(mode: 'side' | 'replace') {
if (this.previewMode === mode) {
this.previewMode = 'none';
} else {
this.previewMode = mode;
}
this.headerOptions.previewMode = this.previewMode;
}The preview renders all GitHub Flavored Markdown (GFM) features:
- Headers (H1-H6)
- Bold, italic, strikethrough
- Links and images
- Ordered and unordered lists
- Task lists with checkboxes
- Code blocks with syntax highlighting
- Inline code
- Blockquotes
- Tables
- Horizontal rules
- Line breaks (GFM mode)
The application supports Light, Dark, and System themes with automatic detection of system color scheme preferences.
- Light Theme - Clean, bright interface with light backgrounds
- Dark Theme - Default dark interface optimized for low-light environments
- System Theme - Automatically follows the operating system's color scheme preference
Theme Service (client/src/services/theme.ts):
- Singleton service managing theme state
- Persists preference to localStorage (
dn-theme-preference) - Listens for system
prefers-color-schemechanges - Applies theme class (
theme-lightortheme-dark) to<html>and<body> - Exports
ThemePreferencetype:'light' | 'dark' | 'system'
CSS Variables (App.vue):
All colors are defined as CSS custom properties:
/* Key CSS Variables */
--main-bg-color: /* Primary background */
--text-primary: /* Main text color */
--text-secondary: /* Secondary text */
--text-link: /* Link color */
--border-color: /* Border color */
--editor-bg: /* Editor background */
--code-bg: /* Code block background */
--syntax-keyword: /* Syntax highlighting */
/* ... and more */Theme Switcher (Settings.vue):
- Located in Settings modal under "Appearance" section
- Three-button selector for Light/Dark/System
- Visual icons: sun, moon, laptop
- Instant theme switching with toast notification
Components should use CSS variables instead of hardcoded colors:
/* Good */
.my-component {
background-color: var(--main-bg-color);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
/* Avoid */
.my-component {
background-color: #263238;
color: #EEFFFF;
}CodeMirror Theme:
The editor uses CSS variables where possible. For HighlightStyle (which doesn't support CSS variables), separate light and dark highlight styles are defined and swapped based on theme.
- Key:
dn-theme-preference - Values:
"light","dark","system" - Default:
"system"(falls back to dark if system preference unavailable)
The Kanban feature provides an optional board view for organizing tasks across customizable columns with drag-and-drop support.
- Go to Settings (gear icon in header menu)
- Find the "Kanban" section
- Toggle "Enable Kanban board"
- When enabled, the Tasks icon in the header changes to a columns icon and opens the Kanban modal
Tasks use standard GFM checkbox syntax with an optional >>column suffix:
- [ ] Plain task → defaults to "todo" column
- [x] Completed task → defaults to "done" column
- [ ] Task in review >>review → explicit "review" column
- [x] Done but still in review >>review → stays in "review" (explicit wins)Column Assignment Rules:
- Explicit
>>columnsyntax always takes precedence over checkbox state - Tasks without explicit column: unchecked → "todo", checked → "done"
- Column names support letters, numbers, and hyphens:
>>in-progress,>>stage2
- Default columns:
["todo", "done"] - Configure custom columns in Settings under the Kanban section
- Add/remove columns, reorder by editing in Settings
Override default columns for a specific note using frontmatter:
---
title: Sprint Planning
kanban:
- backlog
- in-progress
- review
- done
---
- [ ] Task one :backlog:
- [ ] Task two :in-progress:If a task uses a column that doesn't exist in the configuration:
- The column is automatically added before "done"
- Example: Using
:staging:when columns are[todo, done]results in[todo, staging, done] - To reorder, update the columns in Settings or note frontmatter
Backend (app/models.py):
User.kanban_enabled- Boolean to enable/disable Kanban viewUser.kanban_columns- JSON string storing column arrayMeta.task_column- Stores the effective column for each taskTASK_PATTERNregex captures: checkbox state, task text, optional columnparse_tasks_with_columns()- Extracts task info including columnget_task_column()- Determines effective column (explicit > checkbox default)Note.get_kanban_columns()- Returns effective columns (frontmatter > user > system default)
Backend (app/routes.py):
GET /api/settings- Returns kanban settingsPUT /api/settings- Updates kanban_enabled and kanban_columnsPUT /api/task_column- Updates task's column by rewriting markdown/api/sidebar- Includeskanban_enabled,kanban_columns, andtask_columnon tasks
Frontend (client/src/services/sidebar.ts):
kanbanEnabled- Reactive state for kanban togglekanbanColumns- Reactive array of column namestoggleKanban()- Enable/disable kanbanupdateKanbanColumns()- Save new column configurationupdateTaskColumn()- Move task to different column via API
Frontend (client/src/components/Kanban.vue):
- Modal component with column layout
- Native HTML5 drag-and-drop between columns
- Props:
noteId(filter to single note),columns(override columns) - Parses task text to extract display text and completion state
- Shows note title on tasks when viewing all notes
- Responsive design (stacks on mobile)
Frontend (client/src/components/Tasks.vue):
- Conditionally renders dropdown (list) or opens Kanban modal
- Uses
sidebar.kanbanEnabledto determine mode - Shows columns icon when kanban is enabled
Frontend (client/src/components/Settings.vue):
- Kanban section with enable toggle
- Column management UI (add/remove/edit columns)
- Syntax hint for
>>columnusage
-- User table additions
kanban_enabled BOOLEAN DEFAULT FALSE
kanban_columns VARCHAR(512) DEFAULT '["todo", "done"]'
-- Meta table addition
task_column VARCHAR(64) -- Stores effective column for tasksThe Kanban board uses CSS variables for theme compatibility:
--card-bgfor column backgrounds--border-colorfor borders--text-primary,--text-mutedfor text--accent-primaryfor drag-over highlighting
DailyNotes supports password recovery and magic link (passwordless) sign-in via email. These features require SMTP configuration.
Email is optional for users. Users can add their email in Settings to enable password recovery and magic link sign-in.
User Model Addition:
email_encrypted(LargeBinary) - AES encrypted email addressemailhybrid property for transparent encryption/decryption
Tokens are used for password reset and magic link authentication.
AuthToken Model (app/models.py):
class AuthToken(Base):
uuid = Column(GUID, primary_key=True)
user_id = Column(GUID, ForeignKey("user.uuid"))
token_hash = Column(String(128)) # SHA-256 hash
token_type = Column(String(32)) # 'password_reset' or 'magic_link'
created_at = Column(DateTime)
expires_at = Column(DateTime)
used_at = Column(DateTime) # NULL if unusedToken Expiration:
- Password reset: 1 hour
- Magic link: 15 minutes
Security Measures:
- Tokens are cryptographically random (32 bytes, URL-safe)
- Tokens are hashed with SHA-256 before storage
- Single-use tokens (marked used after verification)
- Short expiration times limit exposure window
Prevents abuse of email-sending endpoints.
RateLimit Model (app/models.py):
class RateLimit(Base):
uuid = Column(GUID, primary_key=True)
identifier = Column(String(256)) # Hashed email
action_type = Column(String(32)) # 'password_reset' or 'magic_link'
timestamp = Column(DateTime)Configuration:
- 3 requests per email per hour
- Uses hashed email identifier for privacy
Location: app/email_service.py
Uses aiosmtplib for async email sending.
Key Methods:
send_password_reset(to_email, token)- Sends password reset emailsend_magic_link(to_email, token)- Sends magic link sign-in emailis_enabledproperty - Checks if SMTP is configured
Email Templates:
- HTML emails with inline CSS for compatibility
- Plain text fallback included
- Links to APP_URL-based endpoints
Password Recovery:
-
POST /api/forgot-password- Request password reset email- Request:
{ email: string } - Always returns 200 (prevents email enumeration)
- Returns 429 if rate limited
- Request:
-
POST /api/reset-password- Reset password with token- Request:
{ token: string, password: string } - Returns 200 on success, 400 if invalid/expired
- Request:
Magic Link:
-
POST /api/magic-link- Request magic link email- Request:
{ email: string } - Always returns 200 (prevents email enumeration)
- Returns 429 if rate limited
- Request:
-
POST /api/magic-link/verify- Verify token and get JWT- Request:
{ token: string } - Returns
{ access_token }on success, 400 if invalid/expired
- Request:
Email Management:
-
GET /api/profile- Get user profile with email status- Returns
{ username, has_email, email_masked }
- Returns
-
PUT /api/settings/email- Update user email- Request:
{ email: string | null } - Returns 400 (invalid), 409 (duplicate), 200 (success)
- Request:
New Vue Components:
ForgotPassword.vue- Email input, success messageResetPassword.vue- New password form, token from URL queryMagicLinkRequest.vue- Email input for magic linkMagicLinkVerify.vue- Auto-verifies token, redirects on success
Routes (in Auth layout):
/auth/forgot-password/auth/reset-password?token=.../auth/magic-link/auth/verify-magic-link?token=...
Login.vue Updates:
- Added "Forgot password?" link
- Added "Sign in with email" link
Settings.vue Updates:
- Account section for email management
- Add/change/remove email functionality
- Masked email display for privacy
Environment Variables:
| Variable | Description | Default |
|---|---|---|
SMTP_HOST |
SMTP server hostname | None |
SMTP_PORT |
SMTP server port | 587 |
SMTP_USER |
SMTP username | None |
SMTP_PASSWORD |
SMTP password | None |
SMTP_USE_TLS |
Use TLS | true |
SMTP_FROM_EMAIL |
From address | SMTP_USER |
SMTP_FROM_NAME |
From name | DailyNotes |
APP_URL |
Base URL for email links | http://localhost:8000 |
Config Location: config.py
Location: app/auth_tokens.py
Key Functions:
generate_token()- Creates cryptographically secure tokenhash_token(token)- SHA-256 hash for storagecheck_rate_limit(email, action_type)- Returns True if allowedrecord_rate_limit(email, action_type)- Records attemptcreate_auth_token(user, token_type)- Creates and stores tokenvalidate_token(raw_token, token_type)- Validates and returns userinvalidate_token(raw_token)- Marks token as usedget_user_by_email(email)- Finds user by emailcleanup_expired_tokens()- Removes old tokens
File: migrations/versions/email_and_auth_tokens.py
Adds:
emailcolumn tousertable (LargeBinary, encrypted)auth_tokentable with foreign key to userrate_limittable for tracking requests
- Python version: Requires Python 3.8+ (async/await support)
- Node version: Requires Node 12+, TypeScript strict mode enabled
- Database: Default SQLite but supports PostgreSQL/MySQL via DATABASE_URI
- Encryption: Critical - do not change DB_ENCRYPTION_KEY without migration
- Frontend Build: Built with Vue CLI 4, outputs to
/dist/served by Quart - Linting: Pre-commit hooks run ESLint on Vue/TypeScript files
- Tests: Jest unit tests available in client (minimal coverage currently)
- Docker: Multi-stage build, final image ~400MB
- Date Format: Frontend uses
MM-dd-yyyyformat for daily notes - Title Extraction: Automatic from
title:field in markdown frontmatter - Task Parsing: Uses regex
- \[[x| ]\]to find checkbox tasks - Sidebar State: Managed in
sidebar.tsservice (not Vuex) - Error Handling: Buefy toast notifications for user feedback
- CodeMirror Config: Line numbers, syntax highlighting, code folding
- JWT Expiration: 7 days default (set in config.py)
- Cascading Deletes: User deletion cascades to all notes and metadata
- Preview Hotkeys:
Cmd+KthenVfor side-by-side,Shift+Cmd+Vfor preview-only mode
- Passwords hashed with Argon2 (memory-hard algorithm)
- All user data encrypted at rest with AES
- JWT tokens expire after 7 days
- No CSRF tokens (stateless JWT design)
- Session validation on every request via JWT
- Logout clears token from localStorage
- Consider HTTPS for production deployment
- Environment variables for all secrets (not hardcoded)
- Password reset tokens hashed with SHA-256 and expire after 1 hour
- Magic link tokens hashed with SHA-256 and expire after 15 minutes
- Rate limiting on email endpoints (3 requests/email/hour)
- Email enumeration prevented via consistent responses
- User emails encrypted at rest with AES
Refer to README.md and source code for latest information.