Simple (and self educational) Flask & SQLAlchemy based bookmark and RSS feed app.
Mark.titleis now clamped to 255 characters at the model layer before database writes.- This prevents PostgreSQL errors like
value too long for type character varying(255)across API, quickadd async metadata updates, imports, and UI edits.
"Flaskmarks" is a bookmark managing application. Its purpose is to be a all-in-one bookmark and RSS feed repository. Storing all bookmarks and RSS feeds in one place and make them accessible from all platforms and devices. This is by no means an original idea, but this is an interpretation of the problem.
I have added couple of features I have missed in every bookmarking app: Full Text search of bookmarked links and local copy parsed with python-readability. This can be used as "Read latter" app.
- Bookmarking
- Full text search
- Readability
The easiest way to run Flaskmarks is with Docker.
# Clone the repository
git clone <repo-url>
cd bookmarko
# Copy and configure environment
cp .env.example .env
# Edit .env with your settings (especially POSTGRES_PASSWORD and FLASK_SECRET_KEY)
# Start the application
docker compose up -d
# View logs
docker compose logs -f appThe application will be available at http://localhost:5000
Copy .env.example to .env and configure:
# Required
POSTGRES_PASSWORD=your-secure-password
FLASK_SECRET_KEY=your-secret-key # Generate with: python -c "import secrets; print(secrets.token_hex(32))"
# Optional: Create admin user on first start
FLASKMARKS_ADMIN_USER=admin
FLASKMARKS_ADMIN_PASSWORD=your-admin-password
FLASKMARKS_ADMIN_EMAIL=admin@example.com
# Optional: RAG/AI features
RAG_ENABLED=true
GROQ_API_KEY=your-groq-api-keyGenerate a bcrypt password hash (useful for manual database updates):
python -c "from flaskmarks import create_app; from flaskmarks.core.extensions import bcrypt; app=create_app(); ctx=app.app_context(); ctx.push(); print(bcrypt.generate_password_hash('YOUR_PASSWORD').decode('utf-8')); ctx.pop()"
For development with hot-reloading:
docker compose -f docker-compose.dev.yml upRun the default regression suite locally:
pytestRun tests in Docker:
docker compose exec app pytestUse these checkpoints when deploying route-method and security-control changes.
- Pre-deploy verification:
pytest tests/test_mark_mutation_security.py tests/test_stored_xss_regression.py tests/test_fetch_path_url_targets.py tests/test_config_and_import_status_isolation.py- Deploy and verify route-method hardening:
- Confirm
GET /mark/delete/<id>andGET /mark/increturn405. - Confirm UI delete/click flows still work via form/JS
POSTrequests with CSRF token.
- Verify security controls:
- Confirm stored HTML renders escaped/sanitized (no script execution).
- Confirm private/loopback/link-local fetch targets are rejected.
- Confirm production startup fails fast when required secrets are missing.
- Observe runtime after deploy:
- Watch for spikes in
400CSRF failures (could indicate stale clients/forms). - Watch for unexpected URL validation rejections on legitimate public links.
- If route-method changes break clients:
- Roll back the route-method/template commit(s) first (
/mark/delete,/mark/inc), then redeploy. - Re-run
tests/test_mark_mutation_security.pyto confirm restored behavior.
- If security controls block legitimate behavior:
- Roll back the specific control commit(s) (HTML sanitization, URL target validation, or config guard) instead of full rollback.
- Re-run the matching regression tests before redeploying:
pytest tests/test_stored_xss_regression.py tests/test_fetch_path_url_targets.py tests/test_config_and_import_status_isolation.py- Post-rollback validation:
- Smoke-test add/edit/delete/click flows in UI.
- Confirm app startup and login succeed in the target environment.
# Build and start
docker compose up -d --build
# Stop
docker compose down
# Stop and remove volumes (deletes data!)
docker compose down -v
# View logs
docker compose logs -f
# Run Flask CLI commands
docker compose exec app flask create-user
docker compose exec app flask rag generate-embeddings
# Access PostgreSQL
docker compose exec db psql -U flaskmarks -d flaskmarks
# remove images
docker compose down -v --rmi local
docker compose up -d --build
docker volume ls
# rebuild for start
docker compose -f docker-compose.dev.yml down -v
docker compose -f docker-compose.dev.yml up Docker volumes are used for:
postgres-data: PostgreSQL databaseembeddings-cache: Cached embeddingsmodel-cache: Sentence-transformer model files
$ sudo pip install virtualenv$ virtualenv venv$ . venv/bin/activate
- Create and activate a python virtualenv.
- make a copy of config.py.example to config.py and edit accordingly.
- run:
pip install -r requirements.txt - run:
flask --app flaskmarks db init - run:
flask --app flaskmarks db migrate -m "MIGRATION DESCRIPTION" - run:
flask --app flaskmarks db upgrade - run:
flask --app flaskmarks db - or gunicorn
gunicorn -w 4 'flaskmarks:app'
python3 import nltk nltk.download('punkt') nltk.download('averaged_perceptron_tagger') nltk.download('wordnet')
$ deactivate
Installing this app on a Ubuntu server may take a little more effort than pip install -r requirements.txt. On some systems the following packages need to be installed:
- run:
sudo apt-get install python-virtualenv python2.7-dev build-essential
- run:
python run.py db migrate - run:
python run.py db upgrade
- run:
pip install --upgrade -r requirements.txt
- edit and install examples/flaskmarks.nginx.example
- run:
python run.py runserver -p 5001
There will at any given point be at least two branches in this repository. One master (stable) branch, and one develop branch. The develop branch might contain unfinished code and/or wonky solutions. I will strive to make sure that code merged into master is as stable as possible (given the small size of this application).
- Flask Principal
- Flask SQLAlchemy
- Jinja
- Filters
- Flask and https
- Flask Migrate
- Nice Flask Tutorial
- Flask online book
- Flask Blueprints
- Flask-WhooshAlchemy
- python-readability
Import of firefox bookmark from exported json kinda works. Go to "Show all bookmarks" and than to "Backup".
I have tried this options for keyword extraction. Newspaper3k is the current one.
- https://github.com/codelucas/newspaper - Newspaper3k: Article scraping & curation
- https://radimrehurek.com/gensim/ - Analyze plain-text documents for semantic structure
- https://github.com/csurfer/rake-nltk - Rapid Automatic Keyword Extraction algorithm
- try out http://getskeleton.com css instead of bootstrap, looks much simpler.
PostgreSQL:
CREATE DATABASE bookmarko; CREATE USER bookm_user WITH ENCRYPTED PASSWORD 'digestpass'; GRANT ALL PRIVILEGES ON DATABASE bookmarko TO bookm_user; GRANT USAGE, CREATE ON SCHEMA public TO bookm_user; ALTER DATABASE bookmarko OWNER TO bookm_user;
DATABASE_URL="postgresql://bookm_user:digestpass@localhost:5432/bookmarko"
-- Run this SQL migration ALTER TABLE marks ADD COLUMN search_vector tsvector GENERATED ALWAYS AS ( setweight(to_tsvector('english', coalesce(title, '')), 'A') || setweight(to_tsvector('english', coalesce(full_html, '')), 'B') ) STORED;
-- Create index CREATE INDEX idx_marks_search ON marks USING gin(search_vector);
#LLM
export GROQ_API_KEY=your-api-key-here flask rag generate-embeddings flask rag test-query "what bookmarks do I have about python?" --user-id 1
Flaskmarks provides a RESTful API for programmatic access to bookmarks.
The API uses token-based authentication. Tokens are valid for 24 hours.
# Get a token
curl -X POST http://localhost:5000/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{"username": "your_username", "password": "your_password"}'
# Response:
# {"success": true, "data": {"token": "1:1234567890:abc123...", "expires_in": 86400, "user": {...}}}Use the token in subsequent requests:
curl http://localhost:5000/api/v1/marks \
-H "Authorization: Bearer 1:1234567890:abc123..."| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/auth/token |
Get auth token |
| GET | /api/v1/auth/verify |
Verify token validity |
| POST | /api/v1/auth/refresh |
Refresh token |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/marks |
List marks (paginated) |
| POST | /api/v1/marks |
Create new mark |
| GET | /api/v1/marks/<id> |
Get mark by ID |
| PUT | /api/v1/marks/<id> |
Update mark |
| DELETE | /api/v1/marks/<id> |
Delete mark |
| POST | /api/v1/marks/<id>/click |
Increment click count |
| GET | /api/v1/marks/search?q=<query> |
Search marks |
| GET | /api/v1/marks/by-tag/<tag> |
Get marks by tag |
| GET | /api/v1/marks/stats |
Get statistics |
| GET | /api/v1/marks/export |
Export all marks |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/tags |
List tags with counts |
| POST | /api/v1/tags |
Create tag |
| GET | /api/v1/tags/<id> |
Get tag |
| PUT | /api/v1/tags/<id> |
Rename tag |
| DELETE | /api/v1/tags/<id> |
Remove tag from marks |
| GET | /api/v1/tags/cloud |
Get tag cloud data |
curl -X POST http://localhost:5000/api/v1/marks \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"type": "bookmark",
"title": "Example Site",
"url": "https://example.com",
"description": "An example bookmark",
"tags": ["example", "test"]
}'curl "http://localhost:5000/api/v1/marks/search?q=python&page=1&per_page=20" \
-H "Authorization: Bearer <token>"# Filter by type, paginate, and sort
curl "http://localhost:5000/api/v1/marks?type=bookmark&page=1&per_page=50&sort=created" \
-H "Authorization: Bearer <token>"All API responses follow this format:
Success:
{
"success": true,
"data": { ... },
"message": "Optional message"
}Error:
{
"success": false,
"error": "Error description",
"errors": { "field": "Field-specific error" }
}Flaskmarks includes a browser extension for quickly saving bookmarks from any page.
Since this is a self-hosted extension (not signed by Mozilla), Firefox requires manual installation:
- Download the extension from your Profile page (click "Download for Firefox")
- Open Firefox and go to
about:debugging#/runtime/this-firefox - Click "Load Temporary Add-on..."
- Select the downloaded .xpi file
Note: Temporary add-ons are removed when Firefox restarts.
- Open
about:configin Firefox - Search for
xpinstall.signatures.required - Set it to
false - Download and install the .xpi file directly
You can submit the extension to Mozilla for signing without public listing:
# Install web-ext
npm install -g web-ext
# Sign the extension (requires Mozilla API credentials)
cd browser-extension
web-ext sign --channel=unlisted --api-key=YOUR_KEY --api-secret=YOUR_SECRETThis creates a signed .xpi that installs permanently in any Firefox version.
- Click the Flaskmarks icon in your browser toolbar
- Configure your server URL and API token (from Profile page)
- Click "Save Current Tab" or "Save All Tabs" to bookmark pages
The quick-add endpoint is designed for browser extensions and bookmarklets to save bookmarks with a single click.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/quickadd |
Quick-add a bookmark |
| GET | /api/v1/quickadd |
Quick-add via GET (bookmarklet) |
| GET | /api/v1/quickadd/status/<id> |
Check bookmark status |
| GET | /api/v1/bookmarklet/script?token=<token> |
Generate bookmarklet code |
curl -X POST http://localhost:5000/api/v1/quickadd \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/article",
"title": "Optional Title",
"tags": ["optional", "tags"]
}'{
"success": true,
"data": {
"id": 123,
"url": "https://example.com/article",
"title": "Article Title",
"status": "created",
"metadata_extraction": "pending"
},
"message": "Bookmark saved"
}The endpoint:
- Returns immediately with the bookmark ID
- Triggers async metadata extraction (title, description, content)
- Detects duplicates and returns existing bookmark info
- Supports JSON, form data, and query parameters
- Get your bookmarklet code:
curl "http://localhost:5000/api/v1/bookmarklet/script?token=YOUR_TOKEN"- Create a new bookmark in your browser
- Paste the bookmarklet code as the URL
- Click the bookmark on any page to save it instantly
docker compose down --rmi local -v
docker compose -f docker-compose.dev.yml up --build
docker compose logs -f
docker compose build app