Skip to content

homoludens/bookmarko

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

498 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Flaskmarks

Simple (and self educational) Flask & SQLAlchemy based bookmark and RSS feed app.

Recent Technical Changes

  • Mark.title is 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.

Features

"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

Docker Deployment (Recommended)

The easiest way to run Flaskmarks is with Docker.

Quick Start

# 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 app

The application will be available at http://localhost:5000

Configuration

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-key

Generate 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()"

Development with Docker

For development with hot-reloading:

docker compose -f docker-compose.dev.yml up

Running Tests

Run the default regression suite locally:

pytest

Run tests in Docker:

docker compose exec app pytest

Rollout and Rollback Checkpoints (Route/Security Changes)

Use these checkpoints when deploying route-method and security-control changes.

Rollout

  1. 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
  1. Deploy and verify route-method hardening:
  • Confirm GET /mark/delete/<id> and GET /mark/inc return 405.
  • Confirm UI delete/click flows still work via form/JS POST requests with CSRF token.
  1. 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.
  1. Observe runtime after deploy:
  • Watch for spikes in 400 CSRF failures (could indicate stale clients/forms).
  • Watch for unexpected URL validation rejections on legitimate public links.

Rollback

  1. 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.py to confirm restored behavior.
  1. 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
  1. Post-rollback validation:
  • Smoke-test add/edit/delete/click flows in UI.
  • Confirm app startup and login succeed in the target environment.

Docker Commands

# 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   

Data Persistence

Docker volumes are used for:

  • postgres-data: PostgreSQL database
  • embeddings-cache: Cached embeddings
  • model-cache: Sentence-transformer model files

Manual Installation

Setting up virual envirement

  • $ sudo pip install virtualenv
  • $ virtualenv venv
  • $ . venv/bin/activate

Install

  • 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')

Exit virtualenv

$ deactivate

Ubuntu

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

Upgrade

  • run: python run.py db migrate
  • run: python run.py db upgrade

Package updates

  • run: pip install --upgrade -r requirements.txt

Simple deployment with nginx

  • edit and install examples/flaskmarks.nginx.example
  • run: python run.py runserver -p 5001

Branches

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).

Useful Links

Using

Import of firefox bookmark from exported json kinda works. Go to "Show all bookmarks" and than to "Backup".

Keyword extraction options

I have tried this options for keyword extraction. Newspaper3k is the current one.

TODO

Database

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"

Add full text search for postgresdb

-- 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

REST API

Flaskmarks provides a RESTful API for programmatic access to bookmarks.

Authentication

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..."

API Endpoints

Authentication

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

Marks

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

Tags

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

Examples

Create a bookmark

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"]
  }'

Search bookmarks

curl "http://localhost:5000/api/v1/marks/search?q=python&page=1&per_page=20" \
  -H "Authorization: Bearer <token>"

List marks with filtering

# 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>"

Response Format

All API responses follow this format:

Success:

{
  "success": true,
  "data": { ... },
  "message": "Optional message"
}

Error:

{
  "success": false,
  "error": "Error description",
  "errors": { "field": "Field-specific error" }
}

Browser Extension

Flaskmarks includes a browser extension for quickly saving bookmarks from any page.

Firefox Installation

Since this is a self-hosted extension (not signed by Mozilla), Firefox requires manual installation:

Temporary Installation (Any Firefox)

  1. Download the extension from your Profile page (click "Download for Firefox")
  2. Open Firefox and go to about:debugging#/runtime/this-firefox
  3. Click "Load Temporary Add-on..."
  4. Select the downloaded .xpi file

Note: Temporary add-ons are removed when Firefox restarts.

Permanent Installation (Firefox Developer Edition, Nightly, or ESR)

  1. Open about:config in Firefox
  2. Search for xpinstall.signatures.required
  3. Set it to false
  4. Download and install the .xpi file directly

Optional: Mozilla Signing (for distribution)

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_SECRET

This creates a signed .xpi that installs permanently in any Firefox version.

Extension Usage

  1. Click the Flaskmarks icon in your browser toolbar
  2. Configure your server URL and API token (from Profile page)
  3. Click "Save Current Tab" or "Save All Tabs" to bookmark pages

Quick-Add Endpoint (Browser Extension Support)

The quick-add endpoint is designed for browser extensions and bookmarklets to save bookmarks with a single click.

Endpoint

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

Quick-Add Request

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"]
  }'

Response

{
  "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

Bookmarklet Setup

  1. Get your bookmarklet code:
curl "http://localhost:5000/api/v1/bookmarklet/script?token=YOUR_TOKEN"
  1. Create a new bookmark in your browser
  2. Paste the bookmarklet code as the URL
  3. Click the bookmark on any page to save it instantly

Docker

delete all

docker compose down --rmi local -v

start Dev

docker compose -f docker-compose.dev.yml up --build

logs

docker compose logs -f

prod

docker compose build app

About

My simple and self educational Flask (Python) based bookmarks and RSS feeds app.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Python 47.3%
  • HTML 26.1%
  • CSS 19.2%
  • JavaScript 6.2%
  • Shell 0.7%
  • Dockerfile 0.4%
  • Mako 0.1%